From cd5ed9a0eb2b2bb3a3e2cc09fd7140e9d948a4bb Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Mon, 20 May 2024 17:41:06 +0800 Subject: [PATCH 01/13] build: add MIT license classifier --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a621dd4..49fa048 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ readme = "README.md" requires-python = ">=3.7" classifiers = [ "Programming Language :: Python :: 3", - # "License :: OSI Approved :: MIT License", + "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] keywords = ["LLM", "Large Language Model", "Prompt", "OpenAI", "API"] From b5b310ce1f0a31c58891dca19ebe459e318bcfdd Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 01:41:09 +0800 Subject: [PATCH 02/13] feat(cli): support version output --- src/handyllm/cli.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/handyllm/cli.py b/src/handyllm/cli.py index 780c2c9..ab1f6b7 100644 --- a/src/handyllm/cli.py +++ b/src/handyllm/cli.py @@ -1,6 +1,18 @@ import argparse +import sys +def get_version(): + package_name = __name__.split(".")[0] + if sys.version_info >= (3, 8): + # if python 3.8 or later, use importlib.metadata + import importlib.metadata + return importlib.metadata.version(package_name) + else: + # if older python, use pkg_resources + import pkg_resources + return pkg_resources.get_distribution(package_name).version + def register_hprompt_command(subparsers: argparse._SubParsersAction): parser_hprompt = subparsers.add_parser( 'hprompt', @@ -15,7 +27,6 @@ def register_hprompt_command(subparsers: argparse._SubParsersAction): parser_hprompt.add_argument("-vmp", "--var-map-path", help="Variable map file path") def hprompt_command(args): - import sys from handyllm import hprompt run_config = hprompt.RunConfig() @@ -48,6 +59,11 @@ def cli(): description="HandyLLM CLI", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) + parser.add_argument( + "-v", "--version", + action="version", + version=get_version(), + ) subparsers = parser.add_subparsers(dest="command") register_hprompt_command(subparsers) args = parser.parse_args() From 05bdd930ec69c424dacc55b35ca36b376666dfc8 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 01:42:18 +0800 Subject: [PATCH 03/13] feat(cli): output version in the help message --- src/handyllm/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handyllm/cli.py b/src/handyllm/cli.py index ab1f6b7..82c3a5a 100644 --- a/src/handyllm/cli.py +++ b/src/handyllm/cli.py @@ -56,7 +56,7 @@ def cli(): """Main entry point for the handyllm CLI.""" parser = argparse.ArgumentParser( prog="handyllm", - description="HandyLLM CLI", + description="HandyLLM CLI" + f" v{get_version()}", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( From 39b8be4d346899783df80bb3eed56a2553197f1b Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 13:42:08 +0800 Subject: [PATCH 04/13] refactor: rename ChatPrompt.chat to ChatPrompt.messages --- src/handyllm/hprompt.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/handyllm/hprompt.py b/src/handyllm/hprompt.py index 77e6d97..67c3a00 100644 --- a/src/handyllm/hprompt.py +++ b/src/handyllm/hprompt.py @@ -84,8 +84,8 @@ def loads( if api == "completions": return CompletionsPrompt(content, request, meta, base_path) else: - chat = converter.raw2chat(content) - return ChatPrompt(chat, request, meta, base_path) + messages = converter.raw2chat(content) + return ChatPrompt(messages, request, meta, base_path) def load( fd: io.IOBase, @@ -558,25 +558,25 @@ def _parse_var_map(self, run_config: RunConfig): class ChatPrompt(HandyPrompt): def __init__( - self, chat: list, request: dict, meta: Union[dict, RunConfig], + self, messages: list, request: dict, meta: Union[dict, RunConfig], base_path: Optional[PathType] = None, response: Optional[dict] = None, ): - super().__init__(chat, request, meta, base_path, response) + super().__init__(messages, request, meta, base_path, response) @property - def chat(self) -> list: + def messages(self) -> list: return self.data - @chat.setter - def chat(self, value: list): + @messages.setter + def messages(self, value: list): self.data = value @property def result_str(self) -> str: - if len(self.chat) == 0: + if len(self.messages) == 0: return "" - return self.chat[-1]['content'] + return self.messages[-1]['content'] def _serialize_data(self, data) -> str: return converter.chat2raw(data) @@ -585,9 +585,9 @@ def _eval_data(self, run_config: RunConfig) -> list: var_map = self._parse_var_map(run_config) if var_map: return converter.chat_replace_variables( - self.chat, var_map, inplace=False) + self.messages, var_map, inplace=False) else: - return self.chat + return self.messages def _run_with_client( self, client: OpenAIClient, @@ -663,14 +663,14 @@ def __add__(self, other: Union[str, list, ChatPrompt]): # support concatenation with string, list or another ChatPrompt if isinstance(other, str): return ChatPrompt( - self.chat + [{"role": "user", "content": other}], + self.messages + [{"role": "user", "content": other}], copy.deepcopy(self.request), replace(self.run_config), self.base_path ) elif isinstance(other, list): return ChatPrompt( - self.chat + [{"role": msg['role'], "content": msg['content']} for msg in other], + self.messages + [{"role": msg['role'], "content": msg['content']} for msg in other], copy.deepcopy(self.request), replace(self.run_config), self.base_path @@ -679,7 +679,7 @@ def __add__(self, other: Union[str, list, ChatPrompt]): # merge two ChatPrompt objects merged_request, merged_run_config = self._merge_non_data(other) return ChatPrompt( - self.chat + other.chat, merged_request, merged_run_config, + self.messages + other.messages, merged_request, merged_run_config, self.base_path ) else: @@ -688,12 +688,12 @@ def __add__(self, other: Union[str, list, ChatPrompt]): def __iadd__(self, other: Union[str, list, ChatPrompt]): # support concatenation with string, list or another ChatPrompt if isinstance(other, str): - self.chat.append({"role": "user", "content": other}) + self.messages.append({"role": "user", "content": other}) elif isinstance(other, list): - self.chat += [{"role": msg['role'], "content": msg['content']} for msg in other] + self.messages += [{"role": msg['role'], "content": msg['content']} for msg in other] elif isinstance(other, ChatPrompt): # merge two ChatPrompt objects - self.chat += other.chat + self.messages += other.messages self._merge_non_data(other, inplace=True) else: raise TypeError(f"unsupported operand type(s) for +: 'ChatPrompt' and '{type(other)}'") From 9f2bbaa4dde88521849fa3e155c120f68e2f421d Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 13:57:38 +0800 Subject: [PATCH 05/13] refactor: rename stream_chat2raw to stream_msgs2raw; rename async versions too --- src/handyllm/hprompt.py | 12 ++++++------ src/handyllm/prompt_converter.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/handyllm/hprompt.py b/src/handyllm/hprompt.py index 67c3a00..62a1931 100644 --- a/src/handyllm/hprompt.py +++ b/src/handyllm/hprompt.py @@ -607,14 +607,14 @@ def _run_with_client( with open(run_config.output_path, 'w', encoding='utf-8') as fout: # dump frontmatter fout.write(self._dumps_frontmatter(new_request, run_config, base_path)) - role, content, tool_calls = converter.stream_chat2raw(stream_chat_all(response), fout) + role, content, tool_calls = converter.stream_msgs2raw(stream_chat_all(response), fout) elif run_config.output_fd: # dump frontmatter, no base_path run_config.output_fd.write(self._dumps_frontmatter(new_request, run_config)) # stream response to a file descriptor - role, content, tool_calls = converter.stream_chat2raw(stream_chat_all(response), run_config.output_fd) + role, content, tool_calls = converter.stream_msgs2raw(stream_chat_all(response), run_config.output_fd) else: - role, content, tool_calls = converter.stream_chat2raw(stream_chat_all(response)) + role, content, tool_calls = converter.stream_msgs2raw(stream_chat_all(response)) else: role = response['choices'][0]['message']['role'] content = response['choices'][0]['message'].get('content') @@ -642,13 +642,13 @@ async def _arun_with_client( # stream response to a file with open(run_config.output_path, 'w', encoding='utf-8') as fout: fout.write(self._dumps_frontmatter(new_request, run_config, base_path)) - role, content, tool_calls = await converter.astream_chat2raw(astream_chat_all(response), fout) + role, content, tool_calls = await converter.astream_msgs2raw(astream_chat_all(response), fout) elif run_config.output_fd: # stream response to a file descriptor run_config.output_fd.write(self._dumps_frontmatter(new_request, run_config)) - role, content, tool_calls = await converter.astream_chat2raw(astream_chat_all(response), run_config.output_fd) + role, content, tool_calls = await converter.astream_msgs2raw(astream_chat_all(response), run_config.output_fd) else: - role, content, tool_calls = await converter.astream_chat2raw(astream_chat_all(response)) + role, content, tool_calls = await converter.astream_msgs2raw(astream_chat_all(response)) else: role = response['choices'][0]['message']['role'] content = response['choices'][0]['message'].get('content') diff --git a/src/handyllm/prompt_converter.py b/src/handyllm/prompt_converter.py index c3a7d2c..a741b62 100644 --- a/src/handyllm/prompt_converter.py +++ b/src/handyllm/prompt_converter.py @@ -102,7 +102,7 @@ def chat2raw(chat): return raw_prompt @staticmethod - def stream_chat2raw(gen_sync, fd: Optional[io.IOBase] = None) -> tuple[str, str]: + def stream_msgs2raw(gen_sync, fd: Optional[io.IOBase] = None) -> tuple[str, str]: # stream response to fd role = "" content = "" @@ -133,7 +133,7 @@ def stream_chat2raw(gen_sync, fd: Optional[io.IOBase] = None) -> tuple[str, str] return role, content, tool_calls @staticmethod - async def astream_chat2raw(gen_async, fd: Optional[io.IOBase] = None) -> tuple[str, str]: + async def astream_msgs2raw(gen_async, fd: Optional[io.IOBase] = None) -> tuple[str, str]: # stream response to fd role = "" content = "" From 8970daa1790cf14af63edf9c1fd900f8d164a83d Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 14:10:43 +0800 Subject: [PATCH 06/13] fix(PromptConverter): tuple type needs to be Tuple in python <3.9 --- src/handyllm/prompt_converter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/handyllm/prompt_converter.py b/src/handyllm/prompt_converter.py index a741b62..10f78ca 100644 --- a/src/handyllm/prompt_converter.py +++ b/src/handyllm/prompt_converter.py @@ -1,6 +1,6 @@ import io import re -from typing import Optional +from typing import Optional, Tuple import yaml @@ -102,7 +102,7 @@ def chat2raw(chat): return raw_prompt @staticmethod - def stream_msgs2raw(gen_sync, fd: Optional[io.IOBase] = None) -> tuple[str, str]: + def stream_msgs2raw(gen_sync, fd: Optional[io.IOBase] = None) -> Tuple[str, str]: # stream response to fd role = "" content = "" @@ -133,7 +133,7 @@ def stream_msgs2raw(gen_sync, fd: Optional[io.IOBase] = None) -> tuple[str, str] return role, content, tool_calls @staticmethod - async def astream_msgs2raw(gen_async, fd: Optional[io.IOBase] = None) -> tuple[str, str]: + async def astream_msgs2raw(gen_async, fd: Optional[io.IOBase] = None) -> Tuple[str, str]: # stream response to fd role = "" content = "" From 0327f5f72d4de01395fc9366e19e23e1b89aea11 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 14:32:28 +0800 Subject: [PATCH 07/13] refactor(PromptConverter): rename chat to msgs, keep aliases for compatibility raw2chat = raw2msgs rawfile2chat = rawfile2msgs chat2raw = msgs2raw chat2rawfile = msgs2rawfile chat_replace_variables = msgs_replace_variables --- src/handyllm/_utils.py | 6 +++--- src/handyllm/hprompt.py | 6 +++--- src/handyllm/prompt_converter.py | 20 +++++++++++++------- tests/test_prompt.py | 9 ++++----- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/handyllm/_utils.py b/src/handyllm/_utils.py index dfa84f6..14eade5 100644 --- a/src/handyllm/_utils.py +++ b/src/handyllm/_utils.py @@ -80,9 +80,9 @@ def exception2err_msg(exception: Exception): def _chat_log_response_final(logger, log_marks, kwargs, messages, start_time, role, content, err_msg=None): end_time = time.perf_counter() duration = end_time - start_time - input_content = PromptConverter.chat2raw(messages) + input_content = PromptConverter.msgs2raw(messages) if not err_msg: - output_content = PromptConverter.chat2raw([{'role': role, 'content': content}]) + output_content = PromptConverter.msgs2raw([{'role': role, 'content': content}]) log_result(logger, "Chat request", duration, log_marks, kwargs, input_content, output_content) else: log_exception(logger, "Chat request", duration, log_marks, kwargs, input_content, err_msg) @@ -137,7 +137,7 @@ def _chat_log_exception(logger, log_marks, kwargs, messages, start_time, excepti if logger is not None: end_time = time.perf_counter() duration = end_time - start_time - input_content = PromptConverter.chat2raw(messages) + input_content = PromptConverter.msgs2raw(messages) err_msg = exception2err_msg(exception) log_exception(logger, "Chat request", duration, log_marks, kwargs, input_content, err_msg) diff --git a/src/handyllm/hprompt.py b/src/handyllm/hprompt.py index 62a1931..6f541e1 100644 --- a/src/handyllm/hprompt.py +++ b/src/handyllm/hprompt.py @@ -84,7 +84,7 @@ def loads( if api == "completions": return CompletionsPrompt(content, request, meta, base_path) else: - messages = converter.raw2chat(content) + messages = converter.raw2msgs(content) return ChatPrompt(messages, request, meta, base_path) def load( @@ -579,12 +579,12 @@ def result_str(self) -> str: return self.messages[-1]['content'] def _serialize_data(self, data) -> str: - return converter.chat2raw(data) + return converter.msgs2raw(data) def _eval_data(self, run_config: RunConfig) -> list: var_map = self._parse_var_map(run_config) if var_map: - return converter.chat_replace_variables( + return converter.msgs_replace_variables( self.messages, var_map, inplace=False) else: return self.messages diff --git a/src/handyllm/prompt_converter.py b/src/handyllm/prompt_converter.py index 10f78ca..85c7f66 100644 --- a/src/handyllm/prompt_converter.py +++ b/src/handyllm/prompt_converter.py @@ -35,7 +35,7 @@ def read_substitute_content(self, path: str): value = blocks[idx+1] self.substitute_map[key] = value.strip() - def raw2chat(self, raw_prompt: str): + def raw2msgs(self, raw_prompt: str): # substitute pre-defined variables for key, value in self.substitute_map.items(): raw_prompt = raw_prompt.replace(key, value) @@ -72,14 +72,14 @@ def raw2chat(self, raw_prompt: str): return chat - def rawfile2chat(self, raw_prompt_path: str): + def rawfile2msgs(self, raw_prompt_path: str): with open(raw_prompt_path, 'r', encoding='utf-8') as fin: raw_prompt = fin.read() - return self.raw2chat(raw_prompt) + return self.raw2msgs(raw_prompt) @staticmethod - def chat2raw(chat): + def msgs2raw(chat): # convert chat format to plain text messages = [] for message in chat: @@ -164,13 +164,13 @@ async def astream_msgs2raw(gen_async, fd: Optional[io.IOBase] = None) -> Tuple[s return role, content, tool_calls @classmethod - def chat2rawfile(cls, chat, raw_prompt_path: str): - raw_prompt = cls.chat2raw(chat) + def msgs2rawfile(cls, chat, raw_prompt_path: str): + raw_prompt = cls.msgs2raw(chat) with open(raw_prompt_path, 'w', encoding='utf-8') as fout: fout.write(raw_prompt) @staticmethod - def chat_replace_variables(chat, variable_map: dict, inplace=False): + def msgs_replace_variables(chat, variable_map: dict, inplace=False): # replace every variable in chat content if inplace: for message in chat: @@ -197,3 +197,9 @@ def chat_append_msg(chat, content: str, role: str = 'user', inplace=False): new_chat = chat.copy() new_chat.append({"role": role, "content": content}) return new_chat + + raw2chat = raw2msgs + rawfile2chat = rawfile2msgs + chat2raw = msgs2raw + chat2rawfile = msgs2rawfile + chat_replace_variables = msgs_replace_variables diff --git a/tests/test_prompt.py b/tests/test_prompt.py index 1cb2041..7bb3541 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -1,17 +1,16 @@ -import json from handyllm import PromptConverter converter = PromptConverter() converter.read_substitute_content('./assets/substitute.txt') # read substitute map # chat can be used as the message parameter for OpenAI API -chat = converter.rawfile2chat('./assets/prompt.txt') # variables are substituted according to map +chat = converter.rawfile2msgs('./assets/prompt.txt') # variables are substituted according to map # print(json.dumps(chat, indent=2)) -print(converter.chat2raw(chat)) +print(converter.msgs2raw(chat)) print('-----') # variables wrapped in %s can be replaced at runtime -new_chat = converter.chat_replace_variables( +new_chat = converter.msgs_replace_variables( chat, { r'%misc1%': 'Note1: do not use any bad word.', @@ -19,7 +18,7 @@ } ) # print(json.dumps(new_chat, indent=2)) -print(converter.chat2raw(new_chat)) +print(converter.msgs2raw(new_chat)) print(converter.chat_append_msg(new_chat, '''{ "item1": "It is really a good day.", "item2": "Indeed." From 048ad861c485467f6dca0c161104bfcae5dbeb41 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 14:53:16 +0800 Subject: [PATCH 08/13] fix(hprompt.ChatPrompt): use the whole list when doing addition --- src/handyllm/hprompt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handyllm/hprompt.py b/src/handyllm/hprompt.py index 6f541e1..e4d8e3b 100644 --- a/src/handyllm/hprompt.py +++ b/src/handyllm/hprompt.py @@ -670,7 +670,7 @@ def __add__(self, other: Union[str, list, ChatPrompt]): ) elif isinstance(other, list): return ChatPrompt( - self.messages + [{"role": msg['role'], "content": msg['content']} for msg in other], + self.messages + other, copy.deepcopy(self.request), replace(self.run_config), self.base_path @@ -690,7 +690,7 @@ def __iadd__(self, other: Union[str, list, ChatPrompt]): if isinstance(other, str): self.messages.append({"role": "user", "content": other}) elif isinstance(other, list): - self.messages += [{"role": msg['role'], "content": msg['content']} for msg in other] + self.messages += other elif isinstance(other, ChatPrompt): # merge two ChatPrompt objects self.messages += other.messages From e2aa12fcb7fcdd1be5ab8dcef879e02995473098 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 15:01:10 +0800 Subject: [PATCH 09/13] fix(hprompt): os.PathLike is not subscriptable in python < 3.9 --- src/handyllm/hprompt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/handyllm/hprompt.py b/src/handyllm/hprompt.py index e4d8e3b..b3fc8b0 100644 --- a/src/handyllm/hprompt.py +++ b/src/handyllm/hprompt.py @@ -43,7 +43,10 @@ PromptType = TypeVar('PromptType', bound='HandyPrompt') -PathType = Union[str, os.PathLike[str]] +if sys.version_info >= (3, 9): + PathType = Union[str, os.PathLike[str]] +else: + PathType = Union[str, os.PathLike] converter = PromptConverter() handler = frontmatter.YAMLHandler() From 00bc2cc80ce9079e3d5e0b95bd5a7181b32d0089 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 15:02:28 +0800 Subject: [PATCH 10/13] feat(PromptConverter)!: remove chat_append_msg method --- src/handyllm/prompt_converter.py | 10 ---------- tests/test_prompt.py | 4 ---- 2 files changed, 14 deletions(-) diff --git a/src/handyllm/prompt_converter.py b/src/handyllm/prompt_converter.py index 85c7f66..abd3abe 100644 --- a/src/handyllm/prompt_converter.py +++ b/src/handyllm/prompt_converter.py @@ -188,16 +188,6 @@ def msgs_replace_variables(chat, variable_map: dict, inplace=False): new_chat.append(new_message) return new_chat - @staticmethod - def chat_append_msg(chat, content: str, role: str = 'user', inplace=False): - if inplace: - chat.append({"role": role, "content": content}) - return chat - else: - new_chat = chat.copy() - new_chat.append({"role": role, "content": content}) - return new_chat - raw2chat = raw2msgs rawfile2chat = rawfile2msgs chat2raw = msgs2raw diff --git a/tests/test_prompt.py b/tests/test_prompt.py index 7bb3541..f673563 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -19,7 +19,3 @@ ) # print(json.dumps(new_chat, indent=2)) print(converter.msgs2raw(new_chat)) -print(converter.chat_append_msg(new_chat, '''{ - "item1": "It is really a good day.", - "item2": "Indeed." -}''', role='assistant')) From fbfd7f0d0810eef14f66a76616bd53e75ba3dfcb Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 15:04:11 +0800 Subject: [PATCH 11/13] feat(hprompt): prioritize RunConfig.output_fd over output_path --- src/handyllm/hprompt.py | 42 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/handyllm/hprompt.py b/src/handyllm/hprompt.py index b3fc8b0..8d329a1 100644 --- a/src/handyllm/hprompt.py +++ b/src/handyllm/hprompt.py @@ -605,17 +605,17 @@ def _run_with_client( new_request = self._filter_request(new_request, run_config) base_path = Path(run_config.output_path).parent.resolve() if run_config.output_path else None if stream: - if run_config.output_path: + if run_config.output_fd: + # dump frontmatter, no base_path + run_config.output_fd.write(self._dumps_frontmatter(new_request, run_config)) + # stream response to a file descriptor + role, content, tool_calls = converter.stream_msgs2raw(stream_chat_all(response), run_config.output_fd) + elif run_config.output_path: # stream response to a file with open(run_config.output_path, 'w', encoding='utf-8') as fout: # dump frontmatter fout.write(self._dumps_frontmatter(new_request, run_config, base_path)) role, content, tool_calls = converter.stream_msgs2raw(stream_chat_all(response), fout) - elif run_config.output_fd: - # dump frontmatter, no base_path - run_config.output_fd.write(self._dumps_frontmatter(new_request, run_config)) - # stream response to a file descriptor - role, content, tool_calls = converter.stream_msgs2raw(stream_chat_all(response), run_config.output_fd) else: role, content, tool_calls = converter.stream_msgs2raw(stream_chat_all(response)) else: @@ -641,15 +641,15 @@ async def _arun_with_client( new_request = self._filter_request(new_request, run_config) base_path = Path(run_config.output_path).parent.resolve() if run_config.output_path else None if stream: - if run_config.output_path: + if run_config.output_fd: + # stream response to a file descriptor + run_config.output_fd.write(self._dumps_frontmatter(new_request, run_config)) + role, content, tool_calls = await converter.astream_msgs2raw(astream_chat_all(response), run_config.output_fd) + elif run_config.output_path: # stream response to a file with open(run_config.output_path, 'w', encoding='utf-8') as fout: fout.write(self._dumps_frontmatter(new_request, run_config, base_path)) role, content, tool_calls = await converter.astream_msgs2raw(astream_chat_all(response), fout) - elif run_config.output_fd: - # stream response to a file descriptor - run_config.output_fd.write(self._dumps_frontmatter(new_request, run_config)) - role, content, tool_calls = await converter.astream_msgs2raw(astream_chat_all(response), run_config.output_fd) else: role, content, tool_calls = await converter.astream_msgs2raw(astream_chat_all(response)) else: @@ -752,15 +752,15 @@ def _run_with_client( new_request = self._filter_request(new_request, run_config) base_path = Path(run_config.output_path).parent.resolve() if run_config.output_path else None if stream: - if run_config.output_path: + if run_config.output_fd: + # stream response to a file descriptor + run_config.output_fd.write(self._dumps_frontmatter(new_request, run_config)) + content = self._stream_completions_proc(response, run_config.output_fd) + elif run_config.output_path: # stream response to a file with open(run_config.output_path, 'w', encoding='utf-8') as fout: fout.write(self._dumps_frontmatter(new_request, run_config, base_path)) content = self._stream_completions_proc(response, fout) - elif run_config.output_fd: - # stream response to a file descriptor - run_config.output_fd.write(self._dumps_frontmatter(new_request, run_config)) - content = self._stream_completions_proc(response, run_config.output_fd) else: content = self._stream_completions_proc(response) else: @@ -791,15 +791,15 @@ async def _arun_with_client( new_request = self._filter_request(new_request, run_config) base_path = Path(run_config.output_path).parent.resolve() if run_config.output_path else None if stream: - if run_config.output_path: + if run_config.output_fd: + # stream response to a file descriptor + run_config.output_fd.write(self._dumps_frontmatter(new_request, run_config)) + content = await self._astream_completions_proc(response, run_config.output_fd) + elif run_config.output_path: # stream response to a file with open(run_config.output_path, 'w', encoding='utf-8') as fout: fout.write(self._dumps_frontmatter(new_request, run_config, base_path)) content = await self._astream_completions_proc(response, fout) - elif run_config.output_fd: - # stream response to a file descriptor - run_config.output_fd.write(self._dumps_frontmatter(new_request, run_config)) - content = await self._astream_completions_proc(response, run_config.output_fd) else: content = await self._astream_completions_proc(response) else: From 57395a69a80561eb7c75c31eaa16f5f0640057fb Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 15:14:22 +0800 Subject: [PATCH 12/13] chore: rename chat variable names to msgs --- src/handyllm/prompt_converter.py | 34 ++++++++++++++++---------------- tests/test_prompt.py | 16 +++++++-------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/handyllm/prompt_converter.py b/src/handyllm/prompt_converter.py index abd3abe..bee80cb 100644 --- a/src/handyllm/prompt_converter.py +++ b/src/handyllm/prompt_converter.py @@ -40,8 +40,8 @@ def raw2msgs(self, raw_prompt: str): for key, value in self.substitute_map.items(): raw_prompt = raw_prompt.replace(key, value) - # convert plain text to chat format - chat = [] + # convert plain text to messages format + msgs = [] blocks = re.split(self.split_pattern, raw_prompt, flags=re.MULTILINE) for idx in range(1, len(blocks), 3): role = blocks[idx] @@ -68,9 +68,9 @@ def raw2msgs(self, raw_prompt: str): msg['content'] = yaml.safe_load(content) for key in extra_properties: msg[key] = extra_properties[key] - chat.append(msg) + msgs.append(msg) - return chat + return msgs def rawfile2msgs(self, raw_prompt_path: str): with open(raw_prompt_path, 'r', encoding='utf-8') as fin: @@ -79,10 +79,10 @@ def rawfile2msgs(self, raw_prompt_path: str): return self.raw2msgs(raw_prompt) @staticmethod - def msgs2raw(chat): - # convert chat format to plain text + def msgs2raw(msgs): + # convert messages format to plain text messages = [] - for message in chat: + for message in msgs: role = message.get('role') content = message.get('content') tool_calls = message.get('tool_calls') @@ -164,29 +164,29 @@ async def astream_msgs2raw(gen_async, fd: Optional[io.IOBase] = None) -> Tuple[s return role, content, tool_calls @classmethod - def msgs2rawfile(cls, chat, raw_prompt_path: str): - raw_prompt = cls.msgs2raw(chat) + def msgs2rawfile(cls, msgs, raw_prompt_path: str): + raw_prompt = cls.msgs2raw(msgs) with open(raw_prompt_path, 'w', encoding='utf-8') as fout: fout.write(raw_prompt) @staticmethod - def msgs_replace_variables(chat, variable_map: dict, inplace=False): - # replace every variable in chat content + def msgs_replace_variables(msgs, variable_map: dict, inplace=False): + # replace every variable in messages content if inplace: - for message in chat: + for message in msgs: for var, value in variable_map.items(): if message.get('content') and var in message['content']: message['content'] = message['content'].replace(var, value) - return chat + return msgs else: - new_chat = [] - for message in chat: + new_msgs = [] + for message in msgs: new_message = message.copy() for var, value in variable_map.items(): if new_message.get('content') and var in new_message['content']: new_message['content'] = new_message['content'].replace(var, value) - new_chat.append(new_message) - return new_chat + new_msgs.append(new_message) + return new_msgs raw2chat = raw2msgs rawfile2chat = rawfile2msgs diff --git a/tests/test_prompt.py b/tests/test_prompt.py index f673563..1815665 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -3,19 +3,19 @@ converter.read_substitute_content('./assets/substitute.txt') # read substitute map -# chat can be used as the message parameter for OpenAI API -chat = converter.rawfile2msgs('./assets/prompt.txt') # variables are substituted according to map -# print(json.dumps(chat, indent=2)) -print(converter.msgs2raw(chat)) +# msgs can be used as the message parameter for OpenAI API +msgs = converter.rawfile2msgs('./assets/prompt.txt') # variables are substituted according to map +# print(json.dumps(msgs, indent=2)) +print(converter.msgs2raw(msgs)) print('-----') # variables wrapped in %s can be replaced at runtime -new_chat = converter.msgs_replace_variables( - chat, +new_msgs = converter.msgs_replace_variables( + msgs, { r'%misc1%': 'Note1: do not use any bad word.', r'%misc2%': 'Note2: be optimistic.', } ) -# print(json.dumps(new_chat, indent=2)) -print(converter.msgs2raw(new_chat)) +# print(json.dumps(new_msgs, indent=2)) +print(converter.msgs2raw(new_msgs)) From 2cf6e604fa20a3a1dbf2a439a8664e323d1ae806 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Wed, 22 May 2024 15:25:01 +0800 Subject: [PATCH 13/13] Bump version to 0.7.3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 49fa048..2ef961e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "HandyLLM" -version = "0.7.2" +version = "0.7.3" authors = [ { name="Atomie CHEN", email="atomic_cwh@163.com" }, ]