diff --git a/README.md b/README.md index fbf9d4c..adad6d0 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,10 @@ new_chat = converter.chat_replace_variables( ) ``` +> [!IMPORTANT] +> +> About the prompt format, each role key (e.g. `$system$` / `$user$` / `$assistant`) should be placed in a separate line. + ### Substitute `PromptConverter` can also substitute placeholder variables like `%output_format%` stored in text files to make multiple prompts modular. A substitute map `substitute.txt` looks like this: diff --git a/pyproject.toml b/pyproject.toml index 7fd575b..6116259 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta" [project] name = "HandyLLM" -version = "0.6.0" +version = "0.6.1" authors = [ { name="Atomie CHEN", email="atomic_cwh@163.com" }, ] description = "A handy toolkit for using LLM." readme = "README.md" -requires-python = ">=3.6" +requires-python = ">=3.7" classifiers = [ "Programming Language :: Python :: 3", # "License :: OSI Approved :: MIT License", diff --git a/src/handyllm/openai_client.py b/src/handyllm/openai_client.py index 4fc16a9..a5a6280 100644 --- a/src/handyllm/openai_client.py +++ b/src/handyllm/openai_client.py @@ -1,11 +1,10 @@ +from __future__ import annotations +from typing import Union, TYPE_CHECKING import os import json import time from enum import Enum, auto import asyncio -from typing import Union -import requests -import httpx from .endpoint_manager import Endpoint, EndpointManager from .requestor import Requestor @@ -65,6 +64,9 @@ def __init__( api_version=None, model_engine_map=None, ) -> None: + self._sync_client = None + self._async_client = None + # convert string to enum if isinstance(mode, str): mode = mode.upper() @@ -73,16 +75,16 @@ def __init__( mode = ClientMode[mode] elif not isinstance(mode, ClientMode): raise TypeError("Invalid client mode specified") - + if mode == ClientMode.SYNC or mode == ClientMode.BOTH: + # lazy import + import requests self._sync_client = requests.Session() - else: - self._sync_client = None if mode == ClientMode.ASYNC or mode == ClientMode.BOTH: + # lazy import + import httpx self._async_client = httpx.AsyncClient() - else: - self._async_client = None self.api_base = api_base self.api_key = api_key diff --git a/src/handyllm/prompt_converter.py b/src/handyllm/prompt_converter.py index 8661eab..e847fd3 100644 --- a/src/handyllm/prompt_converter.py +++ b/src/handyllm/prompt_converter.py @@ -2,9 +2,16 @@ class PromptConverter: + role_keys = ['system', 'user', 'assistant'] + def __init__(self): self.substitute_map = {} + @property + def split_pattern(self): + # build a regex pattern to split the prompt by role keys + return r'^\$(' + '|'.join(self.role_keys) + r')\$$' + def read_substitute_content(self, path: str): # 从文本文件读取所有prompt中需要替换的内容 with open(path, 'r', encoding='utf-8') as fin: @@ -24,11 +31,11 @@ def raw2chat(self, raw_prompt: str): # convert plain text to chat format chat = [] - blocks = re.split(r'(\$\w+\$)', raw_prompt) + blocks = re.split(self.split_pattern, raw_prompt, flags=re.MULTILINE) for idx in range(1, len(blocks), 2): key = blocks[idx] value = blocks[idx+1] - chat.append({"role": key[1:-1], "content": value.strip()}) + chat.append({"role": key, "content": value.strip()}) return chat diff --git a/src/handyllm/requestor.py b/src/handyllm/requestor.py index c0f6bb7..f8a82c6 100644 --- a/src/handyllm/requestor.py +++ b/src/handyllm/requestor.py @@ -1,10 +1,12 @@ +from __future__ import annotations +from typing import Union, TYPE_CHECKING import asyncio import logging import json -from typing import Union -import requests -import httpx import time +if TYPE_CHECKING: + import requests + import httpx from ._constants import _API_TYPES_AZURE diff --git a/tests/test_prompt.py b/tests/test_prompt.py index 8cb50fe..1cb2041 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -2,10 +2,10 @@ from handyllm import PromptConverter converter = PromptConverter() -converter.read_substitute_content('../assets/substitute.txt') # read substitute map +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.rawfile2chat('./assets/prompt.txt') # variables are substituted according to map # print(json.dumps(chat, indent=2)) print(converter.chat2raw(chat)) print('-----')