diff --git a/pyproject.toml b/pyproject.toml index fadf01c..c4b3fba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "llm_kira" -version = "0.2.3" +version = "0.2.4" description = "chatbot client for llm" authors = ["sudoskys "] maintainers = [ diff --git a/src/llm_kira/client/Optimizer.py b/src/llm_kira/client/Optimizer.py index 6fad7f5..e6c9fa3 100644 --- a/src/llm_kira/client/Optimizer.py +++ b/src/llm_kira/client/Optimizer.py @@ -132,7 +132,7 @@ def forgetting_curve(x): # 相似度检索 for i in range(0, len(memory)): ask, reply = MsgFlow.get_content(memory[i], sign=False) - _ask_diff = Utils.cosion_sismilarity(pre=prompt, aft=ask) + _ask_diff = Utils.cosion_similarity(pre=prompt, aft=ask) _ask_diff = _ask_diff * 100 score = _ask_diff if _ask_diff < 90 else 0 if score != 0: @@ -256,7 +256,7 @@ def forgetting_curve(x): # 相似度检索 for i in range(0, len(memory)): ask, reply = MsgFlow.get_content(memory[i], sign=False) - _ask_diff = Utils.cosion_sismilarity(pre=prompt, aft=ask) + _ask_diff = Utils.cosion_similarity(pre=prompt, aft=ask) _ask_diff = _ask_diff * 100 score = _ask_diff if _ask_diff < 90 else 0 if score != 0: diff --git a/src/llm_kira/client/anchor.py b/src/llm_kira/client/anchor.py index a0719cb..627b80b 100644 --- a/src/llm_kira/client/anchor.py +++ b/src/llm_kira/client/anchor.py @@ -328,19 +328,16 @@ async def predict(self, foot=_prompt_foot, token=_llm_result_limit) - # Clean - _prompt_body = [item for item in _prompt_body if item] - # Stick Them _prompt = _prompt + f"\n{self.profile.restart_name}:" if not prompt_iscode: _prompt = _prompt.replace("\n\n", "\n") - # ODO - # logger.warning(_prompt) - # Get - llm_result = await self.llm.run(prompt=_prompt, predict_tokens=predict_tokens, llm_param=llm_param) + llm_result = await self.llm.run(prompt=_prompt, + validate=_prompt_body, + predict_tokens=predict_tokens, + llm_param=llm_param) llm_result: LlmReturn # Parse Result @@ -350,4 +347,4 @@ async def predict(self, return ChatBotReturn(conversation_id=f"{self.profile.conversation_id}", llm=llm_result, ask=prompt_text, - reply=self.llm.parse_reply(llm_result.reply)) + reply=self.llm.parse_reply(llm_result.reply).rstrip("")) diff --git a/src/llm_kira/client/llms/base.py b/src/llm_kira/client/llms/base.py index 83fd16d..a2a123c 100644 --- a/src/llm_kira/client/llms/base.py +++ b/src/llm_kira/client/llms/base.py @@ -46,7 +46,9 @@ def get_token_limit(self) -> int: return 2000 @abstractmethod - def tokenizer(self, text): + def tokenizer(self, text, raw=False) -> Union[int, list]: + if raw: + return [] return len(text) @abstractmethod @@ -80,6 +82,7 @@ def parse_usage(response) -> Optional[int]: @abstractmethod async def run(self, prompt: str, + validate: Union[List[str], None] = None, predict_tokens: int = 500, llm_param: LlmBaseParam = None, ) -> Optional[LlmReturn]: diff --git a/src/llm_kira/client/llms/openai.py b/src/llm_kira/client/llms/openai.py index e278e8f..ab8bb59 100644 --- a/src/llm_kira/client/llms/openai.py +++ b/src/llm_kira/client/llms/openai.py @@ -111,10 +111,12 @@ def __init__(self, profile: Conversation, def get_token_limit(self) -> int: return self.token_limit - def tokenizer(self, text) -> int: + def tokenizer(self, text, raw: bool = False) -> Union[int, list]: gpt_tokenizer = tiktoken.get_encoding("gpt2") - _token = len(gpt_tokenizer.encode(text)) - return _token + _token = gpt_tokenizer.encode(text) + if raw: + return _token + return len(_token) @staticmethod def parse_response(response) -> list: @@ -152,6 +154,7 @@ def resize_sentence(self, text: str, token: int) -> str: return text def resize_context(self, head: list, body: list, foot: list, token: int) -> str: + body = [item for item in body if item] token = token if token > 5 else 5 _head = '\n'.join(head) + "\n" _body = "\n".join(body) + "\n" @@ -188,25 +191,29 @@ def model_context_size(model_name: str) -> int: async def run(self, prompt: str, + validate: Union[List[str], None] = None, predict_tokens: int = 500, llm_param: OpenAiParam = None ) -> LlmReturn: """ 异步的,得到对话上下文 :param predict_tokens: 限制返回字符数量 + :param validate: 惩罚验证列表 :param prompt: 提示词 :param llm_param: 参数表 :return: """ - _request_arg = { "top_p": 1, "n": 1 } + _request_arg: dict + # Kwargs if llm_param: _request_arg.update(llm_param.invocation_params) - + if validate is None: + validate = [] _request_arg.update(model=str(llm_param.model_name), prompt=str(prompt), max_tokens=int(predict_tokens), @@ -217,26 +224,46 @@ async def run(self, f"{self.profile.restart_name}:"], ) - # Penalty - if self.auto_penalty: - # THINK ABOUT HOT CAKE - _frequency_penalty, _presence_penalty, _temperature = Detect().gpt_tendency_arg(prompt=prompt) - # SOME HOT CAKE + # Adjust Penalty + if self.auto_penalty and validate: + # Cook + _frequency_penalty, _presence_penalty, _temperature = Detect().gpt_tendency_arg(prompt=prompt, + memory=validate, + tokenizer=self.tokenizer + ) + # Some Update _request_arg.update({ "frequency_penalty": float(_frequency_penalty), "presence_penalty": float(_presence_penalty), "temperature": float(_temperature), }) - # logit_bias + + # 校准字节参数 if not _request_arg.get("logit_bias"): _request_arg["logit_bias"] = {} _request_arg.pop("logit_bias") - # Req + + # 校准温度和惩罚参数 + if _request_arg.get("frequency_penalty"): + _frequency_penalty = _request_arg["frequency_penalty"] + _frequency_penalty = _frequency_penalty if -2.0 < _frequency_penalty else -1.9 + _frequency_penalty = _frequency_penalty if _frequency_penalty < 2.0 else 1.9 + _request_arg["frequency_penalty"] = _frequency_penalty + + if _request_arg.get("presence_penalty"): + _presence_penalty = _request_arg["presence_penalty"] + _presence_penalty = _presence_penalty if -2.0 < _presence_penalty else -1.9 + _presence_penalty = _presence_penalty if _presence_penalty < 2.0 else 1.9 + _request_arg["presence_penalty"] = _presence_penalty + + if _request_arg.get("temperature"): + _temperature = _request_arg["temperature"] + _request_arg["temperature"] = _temperature if 0 < _temperature < 1 else 0.9 + + # 请求 response = await Completion(api_key=self.__api_key, call_func=self.__call_func).create( **_request_arg ) - - # Reply reply = self.parse_response(response) usage = self.parse_usage(response) return LlmReturn(model_flag=llm_param.model_name, diff --git a/src/llm_kira/client/module/plugin/_plugin_tool.py b/src/llm_kira/client/module/plugin/_plugin_tool.py index 25901f7..2bb88a8 100644 --- a/src/llm_kira/client/module/plugin/_plugin_tool.py +++ b/src/llm_kira/client/module/plugin/_plugin_tool.py @@ -115,7 +115,7 @@ def nlp_filter_list(prompt, material: list): continue _pre = material[i] _afe = material[i + 1] - sim = Utils.cosion_sismilarity(pre=_pre, aft=_afe) + sim = Utils.cosion_similarity(pre=_pre, aft=_afe) if sim > 0.7: _remo = _afe if len(_afe) > len(_pre) else _pre # 移除过于相似的 @@ -129,7 +129,7 @@ def nlp_filter_list(prompt, material: list): material = list(material_.keys()) _top_table = {} for item in material: - _top_table[item] = Utils.cosion_sismilarity(pre=prompt, aft=item) + _top_table[item] = Utils.cosion_similarity(pre=prompt, aft=item) material = {k: v for k, v in _top_table.items() if v > 0.15} # 搜索引擎比相似度算法靠谱所以注释掉了 # material = OrderedDict(sorted(material.items(), key=lambda t: t[1])) diff --git a/src/llm_kira/client/module/plugin/time.py b/src/llm_kira/client/module/plugin/time.py index fa17a09..02be71c 100644 --- a/src/llm_kira/client/module/plugin/time.py +++ b/src/llm_kira/client/module/plugin/time.py @@ -17,7 +17,13 @@ class Week(object): def __init__(self): self._server = None self._text = None - self._time = ["time", "多少天", "几天", "时间", "几点", "今天", "昨天", "明天", "几月", "几月", "几号", + self._time = ["time", "今年", "2022", + "2023", "2024", "year", "day", + "多少天", "几几年", + "几天", "时间", + "几点", "今天", + "昨天", "明天", + "几月", "几号", "几个月", "天前"] diff --git a/src/llm_kira/client/module/plugin/week.py b/src/llm_kira/client/module/plugin/week.py index 11a7a28..569b5ce 100644 --- a/src/llm_kira/client/module/plugin/week.py +++ b/src/llm_kira/client/module/plugin/week.py @@ -18,7 +18,7 @@ def __init__(self): self._server = None self._text = None self._week_list = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] - self._week_key = ["星期", "星期几", "时间", "周几", "周一", "周二", "周三", "周四", "周五", "周六"] + self._week_key = ["星期", "星期几", "week", "时间", "周几", "周一", "周二", "周三", "周四", "周五", "周六"] def requirements(self): return [] diff --git a/src/llm_kira/client/text_analysis_tools/__init__.py b/src/llm_kira/client/text_analysis_tools/__init__.py index 22c7a3c..36c7e89 100644 --- a/src/llm_kira/client/text_analysis_tools/__init__.py +++ b/src/llm_kira/client/text_analysis_tools/__init__.py @@ -14,3 +14,5 @@ from .api.summarization.textrank_summarization import TextRankSummarization from .api.text_similarity.simhash import SimHashSimilarity from .api.sentiment.sentiment import SentimentAnalysis +from .api.text_similarity.edit import EditSimilarity +from .api.text_similarity.cosion import CosionSimilarity diff --git a/src/llm_kira/utils/chat.py b/src/llm_kira/utils/chat.py index 0b3e45b..c3fab1f 100644 --- a/src/llm_kira/utils/chat.py +++ b/src/llm_kira/utils/chat.py @@ -5,6 +5,7 @@ # @Github :sudoskys import re import random +from typing import Union, Callable from .langdetect.langdetect import LangDetector from ..client.text_analysis_tools.api.keywords.tfidf import TfidfKeywords @@ -13,12 +14,20 @@ from ..client.text_analysis_tools.api.summarization.tfidf_summarization import TfidfSummarization from ..client.text_analysis_tools.api.text_similarity.simhash import SimHashSimilarity from ..client.text_analysis_tools.api.text_similarity.cosion import CosionSimilarity +from ..client.text_analysis_tools.api.text_similarity.edit import EditSimilarity from ..client.text_analysis_tools.api.keyphrase.keyphrase import KeyPhraseExtraction import tiktoken gpt_tokenizer = tiktoken.get_encoding("gpt2") +def default_gpt_tokenizer(text, raw: bool = False) -> Union[int, list]: + _token = gpt_tokenizer.encode(text) + if raw: + return _token + return len(_token) + + class Detect(object): @staticmethod def isNeedHelp(sentence) -> bool: @@ -87,59 +96,52 @@ def get_text_language(sentence: str): lang_type = detect(text=sentence.replace("\n", "").replace("\r", ""))[0][0].upper() return lang_type - def gpt_tendency_arg(self, prompt: str, memory: list = None, lang: str = "CN") -> tuple: + def gpt_tendency_arg(self, prompt: str, + memory: list = None, + tokenizer: Callable[[str, bool], Union[int, list]] = default_gpt_tokenizer, + lang: str = "CN") -> tuple: + if memory is None: memory = [] - # 代码 + temperature = 0.9 + frequency_penalty = 0 + presence_penalty = 0 + if self.isCode(sentence=prompt): - temperature = 0.9 - frequency_penalty = 0 - presence_penalty = 0 - return frequency_penalty, presence_penalty, temperature - if self.isNeedHelp(sentence=prompt): - temperature = 0.9 - frequency_penalty = 0 - presence_penalty = 0 return frequency_penalty, presence_penalty, temperature - # 普通情况 - temperature = 0.9 + if self.isNeedHelp(sentence=prompt): + temperature -= 0.2 + frequency_penalty -= 0.1 + presence_penalty -= 0.1 # 控制随机数的精度round(数值,精度) - presence_penalty = 0 + round(random.uniform(-1, 1) / 10, 2) - frequency_penalty = 0 + round(random.uniform(-1, 1) / 10, 2) + # presence_penalty += round(random.uniform(-1, 1) / 10, 2) + # frequency_penalty += round(random.uniform(-1, 1) / 10, 2) _sentiment_score = Utils.sentiment(sentence=prompt).get("score") while _sentiment_score > 1.5 or _sentiment_score < -1.5: _sentiment_score = _sentiment_score / 10 _sentiment_score = 0.1 if 0.05 < _sentiment_score < 0.1 else _sentiment_score _sentiment_score = -0.1 if -0.1 < _sentiment_score < -0.05 else _sentiment_score + # 不谈论新话题 + presence_penalty -= _sentiment_score * 0.4 + # 拒绝重复 + frequency_penalty += _sentiment_score * 0.4 - # NEW 高兴正数,就不扭转 - presence_penalty -= _sentiment_score * 1.2 - - # REPEAT 高兴正数,则采用默认加法惩罚 - frequency_penalty += _sentiment_score * 0.8 - _memory_len = len(memory) - # 对话结束就拒绝复读,扭转为正数! - if _memory_len > 20: - while _memory_len > 20: - _memory_len = _memory_len - 20 - if _memory_len / 20 > 0.7: - frequency_penalty = abs(frequency_penalty) + # 验证记忆体 + if len(memory) > 3: + # 计算回复指数指标 + _token = tokenizer("".join(memory[-4:]), True) + _repeat_score = 2 * (0.8 - len(set(_token)) / len(_token)) + frequency_penalty = frequency_penalty + _repeat_score + print(_repeat_score) - # FIX + # Fix temperature = round(temperature, 1) presence_penalty = round(presence_penalty, 1) frequency_penalty = round(frequency_penalty, 1) - # CHECK - temperature = temperature if 0 < temperature < 1 else 0.9 - - presence_penalty = presence_penalty if -1.8 < presence_penalty else -0.1 - presence_penalty = presence_penalty if presence_penalty < 1.8 else 0.1 - - frequency_penalty = frequency_penalty if -1.8 < frequency_penalty else -0.1 - frequency_penalty = frequency_penalty if frequency_penalty < 1.8 else 0.1 + # Check return frequency_penalty, presence_penalty, temperature @@ -178,7 +180,7 @@ def tfidf_summarization(sentence: str, ratio=0.5): return _sum @staticmethod - def cosion_sismilarity(pre, aft): + def cosion_similarity(pre, aft): """ 基于余弦计算文本相似性 0 - 1 (1为最相似) :return: 余弦值 @@ -187,6 +189,16 @@ def cosion_sismilarity(pre, aft): _sim = _cos.similarity(pre, aft) return _sim + @staticmethod + def edit_similarity(pre, aft): + """ + 基于余弦计算文本相似性 0 - 1 (1为最相似) + :return: 余弦值 + """ + _cos = EditSimilarity() + _sim = _cos.edit_dist(pre, aft) + return _sim + @staticmethod def simhash_similarity(pre, aft): """ diff --git a/test/test.py b/test/test.py index 9aa1da4..4352017 100644 --- a/test/test.py +++ b/test/test.py @@ -102,7 +102,7 @@ async def chat(): predict_tokens=500, increase="外部增强:每句话后面都要带 “喵”", # parse_reply=None - ) + ) _info = "parse_reply 回调会处理 llm 的回复字段,比如 list 等,传入list,传出 str 的回复。必须是 str。", print(f"id {response.conversation_id}") print(f"ask {response.ask}") @@ -135,6 +135,11 @@ async def Sentiment(): print(response) +async def Sim(): + response = llm_kira.utils.chat.Utils.edit_similarity(pre="4552", aft="1224") + print(response) + + async def KeyParse(): _sentence_list = [ "《压缩毛巾》是部怎样的作品?", @@ -173,6 +178,7 @@ async def Web(): # asyncio.run(Sentiment()) # asyncio.run(KeyParse()) # asyncio.run(GPT2()) +# asyncio.run(Sim()) # asyncio.run(Web()) # print(float(1)) # print(int(1.2))