diff --git a/.github/workflows/make_test_ebook.yaml b/.github/workflows/make_test_ebook.yaml index 76d6ac0..4c7b500 100644 --- a/.github/workflows/make_test_ebook.yaml +++ b/.github/workflows/make_test_ebook.yaml @@ -71,7 +71,7 @@ jobs: - name: Rename and Upload ePub if: env.OPENAI_API_KEY != null - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: epub_output path: "test_books/lemo_bilingual.epub" diff --git a/README-CN.md b/README-CN.md index 599bdf9..214be60 100644 --- a/README-CN.md +++ b/README-CN.md @@ -27,6 +27,9 @@ bilingual_book_maker 是一个 AI 翻译工具,使用 ChatGPT 帮助用户制 - 可以使用彩云进行翻译 `--model caiyun --caiyun_key ${caiyun_key}` - 可以使用 Gemini 进行翻译 `--model gemini --gemini_key ${gemini_key}` - 可以使用腾讯交互翻译(免费)进行翻译`--model tencentransmart` +- 可以使用[xAI](https://x.ai)进行翻译`--model xai --xai_key ${xai_key}` +- 可以使用 [Ollama](https://github.com/ollama/ollama) 自托管模型进行翻译,使用 `--ollama_model ${ollama_model_name}` + - 如果 ollama server 不运行在本地,使用 `--api_base http://x.x.x.x:port/v1` 指向 ollama server 地址 - 使用 `--test` 命令如果大家没付费可以加上这个先看看效果(有 limit 稍微有些慢) - 使用 `--language` 指定目标语言,例如: `--language "Simplified Chinese"`,预设值为 `"Simplified Chinese"`. 请阅读 helper message 来查找可用的目标语言: `python make_book.py --help` diff --git a/README.md b/README.md index 605bb26..4f977a1 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,19 @@ Find more info here for using liteLLM: https://github.com/BerriAI/litellm/blob/m - Use `--openai_key` option to specify OpenAI API key. If you have multiple keys, separate them by commas (xxx,xxx,xxx) to reduce errors caused by API call limits. Or, just set environment variable `BBM_OPENAI_API_KEY` instead. - A sample book, `test_books/animal_farm.epub`, is provided for testing purposes. -- The default underlying model is [GPT-3.5-turbo](https://openai.com/blog/introducing-chatgpt-and-whisper-apis), which is used by ChatGPT currently. Use `--model gpt4` to change the underlying model to `GPT4`. +- The default underlying model is [GPT-3.5-turbo](https://openai.com/blog/introducing-chatgpt-and-whisper-apis), which is used by ChatGPT currently. Use `--model gpt4` to change the underlying model to `GPT4`. You can also use `GPT4omini`. - Important to note that `gpt-4` is significantly more expensive than `gpt-4-turbo`, but to avoid bumping into rate limits, we automatically balance queries across `gpt-4-1106-preview`, `gpt-4`, `gpt-4-32k`, `gpt-4-0613`,`gpt-4-32k-0613`. - If you want to use a specific model alias with OpenAI (eg `gpt-4-1106-preview` or `gpt-3.5-turbo-0125`), you can use `--model openai --model_list gpt-4-1106-preview,gpt-3.5-turbo-0125`. `--model_list` takes a comma-separated list of model aliases. - - If using `GPT4`, you can add `--use_context` to add a context paragraph to each passage sent to the model for translation (see below).- support DeepL model [DeepL Translator](https://rapidapi.com/splintPRO/api/dpl-translator) need pay to get the token use `--model deepl --deepl_key ${deepl_key}` -- support DeepL free model `--model deeplfree` -- support Google [Gemini](https://makersuite.google.com/app/apikey) model `--model gemini --gemini_key ${gemini_key}` + - If using chatgptapi, you can add `--use_context` to add a context paragraph to each passage sent to the model for translation (see below). +- Support DeepL model [DeepL Translator](https://rapidapi.com/splintPRO/api/dpl-translator) need pay to get the token use `--model deepl --deepl_key ${deepl_key}` +- Support DeepL free model `--model deeplfree` +- Support Google [Gemini](https://aistudio.google.com/app/apikey) model, use `--model gemini` for Gemini Flash or `--model geminipro` for Gemini Pro. `--gemini_key ${gemini_key}` + - If you want to use a specific model alias with Gemini (eg `gemini-1.5-flash-002` or `gemini-1.5-flash-8b-exp-0924`), you can use `--model gemini --model_list gemini-1.5-flash-002,gemini-1.5-flash-8b-exp-0924`. `--model_list` takes a comma-separated list of model aliases. - Support [Claude](https://console.anthropic.com/docs) model, use `--model claude --claude_key ${claude_key}` - Support [Tencent TranSmart](https://transmart.qq.com) model (Free), use `--model tencentransmart` +- Support [xAI](https://x.ai) model, use `--model xai --xai_key ${xai_key}` +- Support [Ollama](https://github.com/ollama/ollama) self-host models, use `--ollama_model ${ollama_model_name}` + - If ollama server is not running on localhost, use `--api_base http://x.x.x.x:port/v1` to point to the ollama server address - Use `--test` option to preview the result if you haven't paid for the service. Note that there is a limit and it may take some time. - Set the target language like `--language "Simplified Chinese"`. Default target language is `"Simplified Chinese"`. Read available languages by helper message: `python make_book.py --help` @@ -54,7 +59,11 @@ Find more info here for using liteLLM: https://github.com/BerriAI/litellm/blob/m - `--accumulated_num` Wait for how many tokens have been accumulated before starting the translation. gpt3.5 limits the total_token to 4090. For example, if you use --accumulated_num 1600, maybe openai will output 2200 tokens and maybe 200 tokens for other messages in the system messages user messages, 1600+2200+200=4000, So you are close to reaching the limit. You have to choose your own value, there is no way to know if the limit is reached before sending -- `--use_context` prompts the GPT4 model to create a one-paragraph summary. If it's the beginning of the translation, it will summarize the entire passage sent (the size depending on `--accumulated_num`), but if it's any proceeding passage, it will amend the summary to include details from the most recent passage, creating a running one-paragraph context payload of the important details of the entire translated work, which improves consistency of flow and tone of each translation. +- `--use_context` prompts the model to create a three-paragraph summary. If it's the beginning of the translation, it will summarize the entire passage sent (the size depending on `--accumulated_num`). For subsequent passages, it will amend the summary to include details from the most recent passage, creating a running one-paragraph context payload of the important details of the entire translated work. This improves consistency of flow and tone throughout the translation. This option is available for all ChatGPT-compatible models and Gemini models. +- Use `--context_paragraph_limit` to set a limit on the number of context paragraphs when using the `--use_context` option. +- Use `--temperature` to set the temperature parameter for `chatgptapi`/`gpt4`/`claude` models. For example: `--temperature 0.7`. +- Use `--block_size` to merge multiple paragraphs into one block. This may increase accuracy and speed up the process but can disturb the original format. Must be used with `--single_translate`. For example: `--block_size 5`. +- Use `--single_translate` to output only the translated book without creating a bilingual version. - `--translation_style` example: `--translation_style "color: #808080; font-style: italic;"` - `--retranslate` `--retranslate "$translated_filepath" "file_name_in_epub" "start_str" "end_str"(optional)`
Retranslate from start_str to end_str's tag: @@ -75,9 +84,12 @@ python3 make_book.py --book_name test_books/Lex_Fridman_episode_322.srt --openai # Or translate the whole book python3 make_book.py --book_name test_books/animal_farm.epub --openai_key ${openai_key} --language zh-hans -# Or translate the whole book using Gemini +# Or translate the whole book using Gemini flash python3 make_book.py --book_name test_books/animal_farm.epub --gemini_key ${gemini_key} --model gemini +# Use a specific list of Gemini model aliases +python3 make_book.py --book_name test_books/animal_farm.epub --gemini_key ${gemini_key} --model gemini --model_list gemini-1.5-flash-002,gemini-1.5-flash-8b-exp-0924 + # Set env OPENAI_API_KEY to ignore option --openai_key export OPENAI_API_KEY=${your_api_key} diff --git a/book_maker/cli.py b/book_maker/cli.py index 892c241..f768440 100644 --- a/book_maker/cli.py +++ b/book_maker/cli.py @@ -122,6 +122,14 @@ def main(): help="You can get Groq Key from https://console.groq.com/keys", ) + # for xAI + parser.add_argument( + "--xai_key", + dest="xai_key", + type=str, + help="You can get xAI Key from https://console.x.ai/", + ) + parser.add_argument( "--test", dest="test", @@ -149,7 +157,7 @@ def main(): "--ollama_model", dest="ollama_model", type=str, - default="ollama_model", + default="", metavar="MODEL", help="use ollama", ) @@ -279,11 +287,18 @@ So you are close to reaching the limit. You have to choose your own value, there action="store_true", help="adds an additional paragraph for global, updating historical context of the story to the model's input, improving the narrative consistency for the AI model (this uses ~200 more tokens each time)", ) + parser.add_argument( + "--context_paragraph_limit", + dest="context_paragraph_limit", + type=int, + default=0, + help="if use --use_context, set context paragraph limit", + ) parser.add_argument( "--temperature", type=float, default=1.0, - help="temperature parameter for `chatgptapi`/`gpt4`/`claude`", + help="temperature parameter for `chatgptapi`/`gpt4`/`claude`/`gemini`", ) parser.add_argument( "--block_size", @@ -297,11 +312,32 @@ So you are close to reaching the limit. You have to choose your own value, there dest="model_list", help="Rather than using our preset lists of models, specify exactly the models you want as a comma separated list `gpt-4-32k,gpt-3.5-turbo-0125` (Currently only supports: `openai`)", ) + parser.add_argument( + "--batch", + dest="batch_flag", + action="store_true", + help="Enable batch translation using ChatGPT's batch API for improved efficiency", + ) + parser.add_argument( + "--batch-use", + dest="batch_use_flag", + action="store_true", + help="Use pre-generated batch translations to create files. Run with --batch first before using this option", + ) + parser.add_argument( + "--interval", + type=float, + default=0.01, + help="Request interval in seconds (e.g., 0.1 for 100ms). Currently only supported for Gemini models. Default: 0.01", + ) options = parser.parse_args() + if not options.book_name: + print(f"Error: please provide the path of your book using --book_name ") + exit(1) if not os.path.isfile(options.book_name): - print(f"Error: {options.book_name} does not exist.") + print(f"Error: the book {options.book_name!r} does not exist.") exit(1) PROXY = options.proxy @@ -312,7 +348,7 @@ So you are close to reaching the limit. You have to choose your own value, there translate_model = MODEL_DICT.get(options.model) assert translate_model is not None, "unsupported model" API_KEY = "" - if options.model in ["openai", "chatgptapi", "gpt4"]: + if options.model in ["openai", "chatgptapi", "gpt4", "gpt4omini", "gpt4o"]: if OPENAI_API_KEY := ( options.openai_key or env.get( @@ -347,10 +383,12 @@ So you are close to reaching the limit. You have to choose your own value, there API_KEY = options.custom_api or env.get("BBM_CUSTOM_API") if not API_KEY: raise Exception("Please provide custom translate api") - elif options.model == "gemini": + elif options.model in ["gemini", "geminipro"]: API_KEY = options.gemini_key or env.get("BBM_GOOGLE_GEMINI_KEY") elif options.model == "groq": API_KEY = options.groq_key or env.get("BBM_GROQ_API_KEY") + elif options.model == "xai": + API_KEY = options.xai_key or env.get("BBM_XAI_API_KEY") else: API_KEY = "" @@ -429,6 +467,8 @@ So you are close to reaching the limit. You have to choose your own value, there assert options.model in [ "chatgptapi", "gpt4", + "gpt4omini", + "gpt4o", ], "only support chatgptapi for deployment_id" if not options.api_base: raise ValueError("`api_base` must be provided when using `deployment_id`") @@ -439,7 +479,7 @@ So you are close to reaching the limit. You have to choose your own value, there e.translate_model.set_model_list(options.model_list.split(",")) else: raise ValueError( - "When using `openai` model, you must also provide `--model_list`. For default model sets use `--model chatgptapi` or `--model gpt4`", + "When using `openai` model, you must also provide `--model_list`. For default model sets use `--model chatgptapi` or `--model gpt4` or `--model gpt4omini`", ) # TODO refactor, quick fix for gpt4 model if options.model == "chatgptapi": @@ -449,8 +489,26 @@ So you are close to reaching the limit. You have to choose your own value, there e.translate_model.set_gpt35_models() if options.model == "gpt4": e.translate_model.set_gpt4_models() + if options.model == "gpt4omini": + e.translate_model.set_gpt4omini_models() + if options.model == "gpt4o": + e.translate_model.set_gpt4o_models() if options.block_size > 0: e.block_size = options.block_size + if options.batch_flag: + e.batch_flag = options.batch_flag + if options.batch_use_flag: + e.batch_use_flag = options.batch_use_flag + + if options.model in ("gemini", "geminipro"): + e.translate_model.set_interval(options.interval) + if options.model == "gemini": + if options.model_list: + e.translate_model.set_model_list(options.model_list.split(",")) + else: + e.translate_model.set_geminiflash_models() + if options.model == "geminipro": + e.translate_model.set_geminipro_models() e.make_bilingual_book() diff --git a/book_maker/config.py b/book_maker/config.py new file mode 100644 index 0000000..a186a4a --- /dev/null +++ b/book_maker/config.py @@ -0,0 +1,8 @@ +config = { + "translator": { + "chatgptapi": { + "context_paragraph_limit": 3, + "batch_context_update_interval": 50, + } + }, +} diff --git a/book_maker/loader/epub_loader.py b/book_maker/loader/epub_loader.py index b7f0750..c2b1437 100644 --- a/book_maker/loader/epub_loader.py +++ b/book_maker/loader/epub_loader.py @@ -2,6 +2,7 @@ import os import pickle import string import sys +import time from copy import copy from pathlib import Path @@ -33,6 +34,7 @@ class EPUBBookLoader(BaseBookLoader): single_translate=False, context_flag=False, temperature=1.0, + context_paragraph_limit=0, ): self.epub_name = epub_name self.new_epub = epub.EpubBook() @@ -41,6 +43,7 @@ class EPUBBookLoader(BaseBookLoader): language, api_base=model_api_base, context_flag=context_flag, + context_paragraph_limit=context_paragraph_limit, temperature=temperature, **prompt_config_to_kwargs(prompt_config), ) @@ -63,6 +66,8 @@ class EPUBBookLoader(BaseBookLoader): self.only_filelist = "" self.single_translate = single_translate self.block_size = -1 + self.batch_use_flag = False + self.batch_flag = False # monkey patch for # 173 def _write_items_patch(obj): @@ -140,11 +145,18 @@ class EPUBBookLoader(BaseBookLoader): if self.resume and index < p_to_save_len: p.string = self.p_to_save[index] else: + t_text = "" + if self.batch_flag: + self.translate_model.add_to_batch_translate_queue(index, new_p.text) + elif self.batch_use_flag: + t_text = self.translate_model.batch_translate(index) + else: + t_text = self.translate_model.translate(new_p.text) if type(p) == NavigableString: - new_p = self.translate_model.translate(new_p.text) + new_p = t_text self.p_to_save.append(new_p) else: - new_p.string = self.translate_model.translate(new_p.text) + new_p.string = t_text self.p_to_save.append(new_p.text) self.helper.insert_trans( @@ -454,6 +466,18 @@ class EPUBBookLoader(BaseBookLoader): return index + def batch_init_then_wait(self): + name, _ = os.path.splitext(self.epub_name) + if self.batch_flag or self.batch_use_flag: + self.translate_model.batch_init(name) + if self.batch_use_flag: + start_time = time.time() + while not self.translate_model.is_completed_batch(): + print("Batch translation is not completed yet") + time.sleep(2) + if time.time() - start_time > 300: # 5 minutes + raise Exception("Batch translation timed out after 5 minutes") + def make_bilingual_book(self): self.helper = EPUBBookLoaderHelper( self.translate_model, @@ -461,6 +485,7 @@ class EPUBBookLoader(BaseBookLoader): self.translation_style, self.context_flag, ) + self.batch_init_then_wait() new_book = self._make_new_book(self.origin_book) all_items = list(self.origin_book.get_items()) trans_taglist = self.translate_tags.split(",") @@ -518,7 +543,10 @@ class EPUBBookLoader(BaseBookLoader): name, _ = os.path.splitext(self.epub_name) epub.write_epub(f"{name}_bilingual.epub", new_book, {}) name, _ = os.path.splitext(self.epub_name) - epub.write_epub(f"{name}_bilingual.epub", new_book, {}) + if self.batch_flag: + self.translate_model.batch() + else: + epub.write_epub(f"{name}_bilingual.epub", new_book, {}) if self.accumulated_num == 1: pbar.close() except (KeyboardInterrupt, Exception) as e: diff --git a/book_maker/loader/helper.py b/book_maker/loader/helper.py index b628f4f..ba5485e 100644 --- a/book_maker/loader/helper.py +++ b/book_maker/loader/helper.py @@ -1,7 +1,7 @@ import re -from copy import copy import backoff import logging +from copy import copy logging.basicConfig(level=logging.WARNING) logger = logging.getLogger(__name__) @@ -37,9 +37,10 @@ class EPUBBookLoaderHelper: Exception, on_backoff=lambda details: logger.warning(f"retry backoff: {details}"), on_giveup=lambda details: logger.warning(f"retry abort: {details}"), + jitter=None, ) - def translate_with_backoff(self, **kwargs): - return self.translate_model.translate(**kwargs) + def translate_with_backoff(self, text, context_flag=False): + return self.translate_model.translate(text, context_flag) def deal_new(self, p, wait_p_list, single_translate=False): self.deal_old(wait_p_list, single_translate, self.context_flag) diff --git a/book_maker/translator/__init__.py b/book_maker/translator/__init__.py index d3efcc3..003dbb8 100644 --- a/book_maker/translator/__init__.py +++ b/book_maker/translator/__init__.py @@ -8,19 +8,24 @@ from book_maker.translator.gemini_translator import Gemini from book_maker.translator.groq_translator import GroqClient from book_maker.translator.tencent_transmart_translator import TencentTranSmart from book_maker.translator.custom_api_translator import CustomAPI +from book_maker.translator.xai_translator import XAIClient MODEL_DICT = { "openai": ChatGPTAPI, "chatgptapi": ChatGPTAPI, "gpt4": ChatGPTAPI, + "gpt4omini": ChatGPTAPI, + "gpt4o": ChatGPTAPI, "google": Google, "caiyun": Caiyun, "deepl": DeepL, "deeplfree": DeepLFree, "claude": Claude, "gemini": Gemini, + "geminipro": Gemini, "groq": GroqClient, "tencentransmart": TencentTranSmart, "customapi": CustomAPI, + "xai": XAIClient, # add more here } diff --git a/book_maker/translator/chatgptapi_translator.py b/book_maker/translator/chatgptapi_translator.py index bc83d8c..647bae4 100644 --- a/book_maker/translator/chatgptapi_translator.py +++ b/book_maker/translator/chatgptapi_translator.py @@ -1,13 +1,19 @@ import re import time +import os +import shutil from copy import copy from os import environ from itertools import cycle +import json from openai import AzureOpenAI, OpenAI, RateLimitError from rich import print from .base_translator import Base +from ..config import config + +CHATGPT_CONFIG = config["translator"]["chatgptapi"] PROMPT_ENV_MAP = { "user": "BBM_CHATGPTAPI_USER_MSG_TEMPLATE", @@ -27,10 +33,22 @@ GPT4_MODEL_LIST = [ "gpt-4-1106-preview", "gpt-4", "gpt-4-32k", + "gpt-4o-2024-05-13", "gpt-4-0613", "gpt-4-32k-0613", ] +GPT4oMINI_MODEL_LIST = [ + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", +] +GPT4o_MODEL_LIST = [ + "gpt-4o", + "gpt-4o-2024-05-13", + "gpt-4o-2024-08-06", + "chatgpt-4o-latest", +] + class ChatGPTAPI(Base): DEFAULT_PROMPT = "Please help me to translate,`{text}` to {language}, please return only translated content not include the origin text" @@ -43,6 +61,8 @@ class ChatGPTAPI(Base): prompt_template=None, prompt_sys_msg=None, temperature=1.0, + context_flag=False, + context_paragraph_limit=0, **kwargs, ) -> None: super().__init__(key, language) @@ -67,6 +87,18 @@ class ChatGPTAPI(Base): self.deployment_id = None self.temperature = temperature self.model_list = None + self.context_flag = context_flag + self.context_list = [] + self.context_translated_list = [] + if context_paragraph_limit > 0: + # not set by user, use default + self.context_paragraph_limit = context_paragraph_limit + else: + # set by user, use user's value + self.context_paragraph_limit = CHATGPT_CONFIG["context_paragraph_limit"] + self.batch_text_list = [] + self.batch_info_cache = None + self.result_content_cache = {} def rotate_key(self): self.openai_client.api_key = next(self.keys) @@ -74,16 +106,36 @@ class ChatGPTAPI(Base): def rotate_model(self): self.model = next(self.model_list) - def create_chat_completion(self, text): + def create_messages(self, text, intermediate_messages=None): content = self.prompt_template.format( text=text, language=self.language, crlf="\n" ) + sys_content = self.system_content or self.prompt_sys_msg.format(crlf="\n") messages = [ {"role": "system", "content": sys_content}, - {"role": "user", "content": content}, ] + if intermediate_messages: + messages.extend(intermediate_messages) + + messages.append({"role": "user", "content": content}) + return messages + + def create_context_messages(self): + messages = [] + if self.context_flag: + messages.append({"role": "user", "content": "\n".join(self.context_list)}) + messages.append( + { + "role": "assistant", + "content": "\n".join(self.context_translated_list), + } + ) + return messages + + def create_chat_completion(self, text): + messages = self.create_messages(text, self.create_context_messages()) completion = self.openai_client.chat.completions.create( model=self.model, messages=messages, @@ -104,8 +156,20 @@ class ChatGPTAPI(Base): else: t_text = "" + if self.context_flag: + self.save_context(text, t_text) + return t_text + def save_context(self, text, t_text): + if self.context_paragraph_limit > 0: + self.context_list.append(text) + self.context_translated_list.append(t_text) + # Remove the oldest context + if len(self.context_list) > self.context_paragraph_limit: + self.context_list.pop(0) + self.context_translated_list.pop(0) + def translate(self, text, needprint=True): start_time = time.time() # todo: Determine whether to print according to the cli option @@ -334,7 +398,252 @@ class ChatGPTAPI(Base): print(f"Using model list {model_list}") self.model_list = cycle(model_list) + def set_gpt4omini_models(self): + # for issue #375 azure can not use model list + if self.deployment_id: + self.model_list = cycle(["gpt-4o-mini"]) + else: + my_model_list = [ + i["id"] for i in self.openai_client.models.list().model_dump()["data"] + ] + model_list = list(set(my_model_list) & set(GPT4oMINI_MODEL_LIST)) + print(f"Using model list {model_list}") + self.model_list = cycle(model_list) + + def set_gpt4o_models(self): + # for issue #375 azure can not use model list + if self.deployment_id: + self.model_list = cycle(["gpt-4o"]) + else: + my_model_list = [ + i["id"] for i in self.openai_client.models.list().model_dump()["data"] + ] + model_list = list(set(my_model_list) & set(GPT4o_MODEL_LIST)) + print(f"Using model list {model_list}") + self.model_list = cycle(model_list) + def set_model_list(self, model_list): model_list = list(set(model_list)) print(f"Using model list {model_list}") self.model_list = cycle(model_list) + + def batch_init(self, book_name): + self.book_name = self.sanitize_book_name(book_name) + + def add_to_batch_translate_queue(self, book_index, text): + self.batch_text_list.append({"book_index": book_index, "text": text}) + + def sanitize_book_name(self, book_name): + # Replace any characters that are not alphanumeric, underscore, hyphen, or dot with an underscore + sanitized_book_name = re.sub(r"[^\w\-_\.]", "_", book_name) + # Remove leading and trailing underscores and dots + sanitized_book_name = sanitized_book_name.strip("._") + return sanitized_book_name + + def batch_metadata_file_path(self): + return os.path.join(os.getcwd(), "batch_files", f"{self.book_name}_info.json") + + def batch_dir(self): + return os.path.join(os.getcwd(), "batch_files", self.book_name) + + def custom_id(self, book_index): + return f"{self.book_name}-{book_index}" + + def is_completed_batch(self): + batch_metadata_file_path = self.batch_metadata_file_path() + + if not os.path.exists(batch_metadata_file_path): + print("Batch result file does not exist") + raise Exception("Batch result file does not exist") + + with open(batch_metadata_file_path, "r", encoding="utf-8") as f: + batch_info = json.load(f) + + for batch_file in batch_info["batch_files"]: + batch_status = self.check_batch_status(batch_file["batch_id"]) + if batch_status.status != "completed": + return False + + return True + + def batch_translate(self, book_index): + if self.batch_info_cache is None: + batch_metadata_file_path = self.batch_metadata_file_path() + with open(batch_metadata_file_path, "r", encoding="utf-8") as f: + self.batch_info_cache = json.load(f) + + batch_info = self.batch_info_cache + target_batch = None + for batch in batch_info["batch_files"]: + if batch["start_index"] <= book_index < batch["end_index"]: + target_batch = batch + break + + if not target_batch: + raise ValueError(f"No batch found for book_index {book_index}") + + if target_batch["batch_id"] in self.result_content_cache: + result_content = self.result_content_cache[target_batch["batch_id"]] + else: + batch_status = self.check_batch_status(target_batch["batch_id"]) + if batch_status.output_file_id is None: + raise ValueError(f"Batch {target_batch['batch_id']} is not completed") + result_content = self.get_batch_result(batch_status.output_file_id) + self.result_content_cache[target_batch["batch_id"]] = result_content + + result_lines = result_content.text.split("\n") + custom_id = self.custom_id(book_index) + for line in result_lines: + if line.strip(): + result = json.loads(line) + if result["custom_id"] == custom_id: + return result["response"]["body"]["choices"][0]["message"][ + "content" + ] + + raise ValueError(f"No result found for custom_id {custom_id}") + + def create_batch_context_messages(self, index): + messages = [] + if self.context_flag: + if index % CHATGPT_CONFIG[ + "batch_context_update_interval" + ] == 0 or not hasattr(self, "cached_context_messages"): + context_messages = [] + for i in range(index - 1, -1, -1): + item = self.batch_text_list[i] + if len(item["text"].split()) >= 100: + context_messages.append(item["text"]) + if len(context_messages) == self.context_paragraph_limit: + break + + if len(context_messages) == self.context_paragraph_limit: + print("Creating cached context messages") + self.cached_context_messages = [ + {"role": "user", "content": "\n".join(context_messages)}, + { + "role": "assistant", + "content": self.get_translation( + "\n".join(context_messages) + ), + }, + ] + + if hasattr(self, "cached_context_messages"): + messages.extend(self.cached_context_messages) + + return messages + + def make_batch_request(self, book_index, text): + messages = self.create_messages( + text, self.create_batch_context_messages(book_index) + ) + return { + "custom_id": self.custom_id(book_index), + "method": "POST", + "url": "/v1/chat/completions", + "body": { + # model shuould not be rotate + "model": self.batch_model, + "messages": messages, + "temperature": self.temperature, + }, + } + + def create_batch_files(self, dest_file_path): + file_paths = [] + # max request 50,000 and max size 100MB + lines_per_file = 40000 + current_file = 0 + + for i in range(0, len(self.batch_text_list), lines_per_file): + current_file += 1 + file_path = os.path.join(dest_file_path, f"{current_file}.jsonl") + start_index = i + end_index = i + lines_per_file + + # TODO: Split the file if it exceeds 100MB + with open(file_path, "w", encoding="utf-8") as f: + for text in self.batch_text_list[i : i + lines_per_file]: + batch_req = self.make_batch_request( + text["book_index"], text["text"] + ) + json.dump(batch_req, f, ensure_ascii=False) + f.write("\n") + file_paths.append( + { + "file_path": file_path, + "start_index": start_index, + "end_index": end_index, + } + ) + + return file_paths + + def batch(self): + self.rotate_model() + self.batch_model = self.model + # current working directory + batch_dir = self.batch_dir() + batch_metadata_file_path = self.batch_metadata_file_path() + # cleanup batch dir and result file + if os.path.exists(batch_dir): + shutil.rmtree(batch_dir) + if os.path.exists(batch_metadata_file_path): + os.remove(batch_metadata_file_path) + os.makedirs(batch_dir, exist_ok=True) + # batch execute + batch_files = self.create_batch_files(batch_dir) + batch_info = [] + for batch_file in batch_files: + file_id = self.upload_batch_file(batch_file["file_path"]) + batch = self.batch_execute(file_id) + batch_info.append( + self.create_batch_info( + file_id, batch, batch_file["start_index"], batch_file["end_index"] + ) + ) + # save batch info + batch_info_json = { + "book_id": self.book_name, + "batch_date": time.strftime("%Y-%m-%d %H:%M:%S"), + "batch_files": batch_info, + } + with open(batch_metadata_file_path, "w", encoding="utf-8") as f: + json.dump(batch_info_json, f, ensure_ascii=False, indent=2) + + def create_batch_info(self, file_id, batch, start_index, end_index): + return { + "input_file_id": file_id, + "batch_id": batch.id, + "start_index": start_index, + "end_index": end_index, + "prefix": self.book_name, + } + + def upload_batch_file(self, file_path): + batch_input_file = self.openai_client.files.create( + file=open(file_path, "rb"), purpose="batch" + ) + return batch_input_file.id + + def batch_execute(self, file_id): + current_time = time.strftime("%Y-%m-%d %H:%M:%S") + res = self.openai_client.batches.create( + input_file_id=file_id, + endpoint="/v1/chat/completions", + completion_window="24h", + metadata={ + "description": f"Batch job for {self.book_name} at {current_time}" + }, + ) + if res.errors: + print(res.errors) + raise Exception(f"Batch execution failed: {res.errors}") + return res + + def check_batch_status(self, batch_id): + return self.openai_client.batches.retrieve(batch_id) + + def get_batch_result(self, output_file_id): + return self.openai_client.files.content(output_file_id) diff --git a/book_maker/translator/gemini_translator.py b/book_maker/translator/gemini_translator.py index 0dc43a6..9a79836 100644 --- a/book_maker/translator/gemini_translator.py +++ b/book_maker/translator/gemini_translator.py @@ -1,5 +1,7 @@ import re import time +from os import environ +from itertools import cycle import google.generativeai as genai from google.generativeai.types.generation_types import ( @@ -11,23 +13,36 @@ from rich import print from .base_translator import Base generation_config = { - "temperature": 0.7, + "temperature": 1.0, "top_p": 1, "top_k": 1, - "max_output_tokens": 2048, + "max_output_tokens": 8192, } -safety_settings = [ - {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}, - {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}, - { - "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", - "threshold": "BLOCK_MEDIUM_AND_ABOVE", - }, - { - "category": "HARM_CATEGORY_DANGEROUS_CONTENT", - "threshold": "BLOCK_MEDIUM_AND_ABOVE", - }, +safety_settings = { + "HATE": "BLOCK_NONE", + "HARASSMENT": "BLOCK_NONE", + "SEXUAL": "BLOCK_NONE", + "DANGEROUS": "BLOCK_NONE", +} + +PROMPT_ENV_MAP = { + "user": "BBM_GEMINIAPI_USER_MSG_TEMPLATE", + "system": "BBM_GEMINIAPI_SYS_MSG", +} + +GEMINIPRO_MODEL_LIST = [ + "gemini-1.5-pro", + "gemini-1.5-pro-latest", + "gemini-1.5-pro-001", + "gemini-1.5-pro-002", +] + +GEMINIFLASH_MODEL_LIST = [ + "gemini-1.5-flash", + "gemini-1.5-flash-latest", + "gemini-1.5-flash-001", + "gemini-1.5-flash-002", ] @@ -38,20 +53,57 @@ class Gemini(Base): DEFAULT_PROMPT = "Please help me to translate,`{text}` to {language}, please return only translated content not include the origin text" - def __init__(self, key, language, **kwargs) -> None: - genai.configure(api_key=key) + def __init__( + self, + key, + language, + prompt_template=None, + prompt_sys_msg=None, + context_flag=False, + temperature=1.0, + **kwargs, + ) -> None: super().__init__(key, language) + self.context_flag = context_flag + self.prompt = ( + prompt_template + or environ.get(PROMPT_ENV_MAP["user"]) + or self.DEFAULT_PROMPT + ) + self.prompt_sys_msg = ( + prompt_sys_msg + or environ.get(PROMPT_ENV_MAP["system"]) + or None # Allow None, but not empty string + ) + + genai.configure(api_key=next(self.keys)) + generation_config["temperature"] = temperature + + def create_convo(self): model = genai.GenerativeModel( - model_name="gemini-pro", + model_name=self.model, generation_config=generation_config, safety_settings=safety_settings, + system_instruction=self.prompt_sys_msg, ) self.convo = model.start_chat() + # print(model) # Uncomment to debug and inspect the model details. + + def rotate_model(self): + self.model = next(self.model_list) + self.create_convo() + print(f"Using model {self.model}") def rotate_key(self): - pass + genai.configure(api_key=next(self.keys)) + self.create_convo() def translate(self, text): + delay = 1 + exponential_base = 2 + attempt_count = 0 + max_attempts = 7 + t_text = "" print(text) # same for caiyun translate src issue #279 gemini for #374 @@ -60,32 +112,78 @@ class Gemini(Base): if len(text_list) > 1: if text_list[0].isdigit(): num = text_list[0] - try: - self.convo.send_message( - self.DEFAULT_PROMPT.format(text=text, language=self.language) - ) - print(text) - t_text = self.convo.last.text.strip() - except StopCandidateException as e: - match = re.search(r'content\s*{\s*parts\s*{\s*text:\s*"([^"]+)"', str(e)) - if match: - t_text = match.group(1) - t_text = re.sub(r"\\n", "\n", t_text) - else: - t_text = "Can not translate" - except BlockedPromptException as e: - print(str(e)) - t_text = "Can not translate by SAFETY reason.(因安全问题不能翻译)" - except Exception as e: - print(str(e)) - t_text = "Can not translate by other reason.(因安全问题不能翻译)" - if len(self.convo.history) > 10: - self.convo.history = self.convo.history[2:] + while attempt_count < max_attempts: + try: + self.convo.send_message( + self.prompt.format(text=text, language=self.language) + ) + t_text = self.convo.last.text.strip() + break + except StopCandidateException as e: + print( + f"Translation failed due to StopCandidateException: {e} Attempting to switch model..." + ) + self.rotate_model() + except BlockedPromptException as e: + print( + f"Translation failed due to BlockedPromptException: {e} Attempting to switch model..." + ) + self.rotate_model() + except Exception as e: + print( + f"Translation failed due to {type(e).__name__}: {e} Will sleep {delay} seconds" + ) + time.sleep(delay) + delay *= exponential_base + + self.rotate_key() + if attempt_count >= 1: + self.rotate_model() + + attempt_count += 1 + + if attempt_count == max_attempts: + print(f"Translation failed after {max_attempts} attempts.") + return + + if self.context_flag: + if len(self.convo.history) > 10: + self.convo.history = self.convo.history[2:] + else: + self.convo.history = [] print("[bold green]" + re.sub("\n{3,}", "\n\n", t_text) + "[/bold green]") - # for limit - time.sleep(0.5) + # for rate limit(RPM) + time.sleep(self.interval) if num: t_text = str(num) + "\n" + t_text return t_text + + def set_interval(self, interval): + self.interval = interval + + def set_geminipro_models(self): + self.set_models(GEMINIPRO_MODEL_LIST) + + def set_geminiflash_models(self): + self.set_models(GEMINIFLASH_MODEL_LIST) + + def set_models(self, allowed_models): + available_models = [ + re.sub(r"^models/", "", i.name) for i in genai.list_models() + ] + model_list = sorted( + list(set(available_models) & set(allowed_models)), + key=allowed_models.index, + ) + print(f"Using model list {model_list}") + self.model_list = cycle(model_list) + self.rotate_model() + + def set_model_list(self, model_list): + # keep the order of input + model_list = sorted(list(set(model_list)), key=model_list.index) + print(f"Using model list {model_list}") + self.model_list = cycle(model_list) + self.rotate_model() diff --git a/book_maker/translator/xai_translator.py b/book_maker/translator/xai_translator.py new file mode 100644 index 0000000..faa8332 --- /dev/null +++ b/book_maker/translator/xai_translator.py @@ -0,0 +1,20 @@ +from openai import OpenAI +from .chatgptapi_translator import ChatGPTAPI +from os import linesep +from itertools import cycle + + +XAI_MODEL_LIST = [ + "grok-beta", +] + + +class XAIClient(ChatGPTAPI): + def __init__(self, key, language, api_base=None, **kwargs) -> None: + super().__init__(key, language) + self.model_list = XAI_MODEL_LIST + self.api_url = str(api_base) if api_base else "https://api.x.ai/v1" + self.openai_client = OpenAI(api_key=key, base_url=self.api_url) + + def rotate_model(self): + self.model = self.model_list[0] diff --git a/docs/model_lang.md b/docs/model_lang.md index 5974ffe..336da50 100644 --- a/docs/model_lang.md +++ b/docs/model_lang.md @@ -2,7 +2,7 @@ ## Models `-m, --model `
-Currently `bbook_maker` supports these models: `chatgptapi` , `gpt3` , `google` , `caiyun` , `deepl` , `deeplfree` , `gpt4` , `claude` , `customapi`. +Currently `bbook_maker` supports these models: `chatgptapi` , `gpt3` , `google` , `caiyun` , `deepl` , `deeplfree` , `gpt4` , `gpt4omini` , `claude` , `customapi`. Default model is `chatgptapi` . ### OPENAI models diff --git a/pdm.lock b/pdm.lock index 93121da..a79c3a6 100644 --- a/pdm.lock +++ b/pdm.lock @@ -112,7 +112,7 @@ files = [ [[package]] name = "anthropic" -version = "0.25.7" +version = "0.26.1" requires_python = ">=3.7" summary = "The official Python library for the anthropic API" groups = ["default"] @@ -120,14 +120,15 @@ dependencies = [ "anyio<5,>=3.5.0", "distro<2,>=1.7.0", "httpx<1,>=0.23.0", + "jiter<1,>=0.1.0", "pydantic<3,>=1.9.0", "sniffio", "tokenizers>=0.13.0", "typing-extensions<5,>=4.7", ] files = [ - {file = "anthropic-0.25.7-py3-none-any.whl", hash = "sha256:419a276eb20cfb7ddaac03c7e28e4e12df3ace71bcf33071a68c9a03c0dfcbdd"}, - {file = "anthropic-0.25.7.tar.gz", hash = "sha256:e7de4c8ba8e7e8248ad7f05ed9176634780b95b67c678d23915d8964c8a26f4e"}, + {file = "anthropic-0.26.1-py3-none-any.whl", hash = "sha256:2812b9b250b551ed8a1f0a7e6ae3f005654098994f45ebca5b5808bd154c9628"}, + {file = "anthropic-0.26.1.tar.gz", hash = "sha256:26680ff781a6f678a30a1dccd0743631e602b23a47719439ffdef5335fa167d8"}, ] [[package]] @@ -613,7 +614,7 @@ files = [ [[package]] name = "google-ai-generativelanguage" -version = "0.6.2" +version = "0.6.4" requires_python = ">=3.7" summary = "Google Ai Generativelanguage API client library" groups = ["default"] @@ -624,8 +625,8 @@ dependencies = [ "protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.19.5", ] files = [ - {file = "google-ai-generativelanguage-0.6.2.tar.gz", hash = "sha256:308791ac3b9dad015b359172970739aa3753dd542142a416d07f9fa047e22386"}, - {file = "google_ai_generativelanguage-0.6.2-py3-none-any.whl", hash = "sha256:bf84c34c641570d7e8a1f2e6901e6771af1438f2ee8307d1801fd43585f9b1c6"}, + {file = "google-ai-generativelanguage-0.6.4.tar.gz", hash = "sha256:1750848c12af96cb24ae1c3dd05e4bfe24867dc4577009ed03e1042d8421e874"}, + {file = "google_ai_generativelanguage-0.6.4-py3-none-any.whl", hash = "sha256:730e471aa549797118fb1c88421ba1957741433ada575cf5dd08d3aebf903ab1"}, ] [[package]] @@ -715,12 +716,12 @@ files = [ [[package]] name = "google-generativeai" -version = "0.5.2" +version = "0.5.4" requires_python = ">=3.9" summary = "Google Generative AI High level API client library and tools." groups = ["default"] dependencies = [ - "google-ai-generativelanguage==0.6.2", + "google-ai-generativelanguage==0.6.4", "google-api-core", "google-api-python-client", "google-auth>=2.15.0", @@ -730,7 +731,7 @@ dependencies = [ "typing-extensions", ] files = [ - {file = "google_generativeai-0.5.2-py3-none-any.whl", hash = "sha256:56f39485a0a673c93c21ec31c17809cc6a964193fb77b7ce809ad15d0dd72d7b"}, + {file = "google_generativeai-0.5.4-py3-none-any.whl", hash = "sha256:036d63ee35e7c8aedceda4f81c390a5102808af09ff3a6e57e27ed0be0708f3c"}, ] [[package]] @@ -749,7 +750,7 @@ files = [ [[package]] name = "groq" -version = "0.5.0" +version = "0.8.0" requires_python = ">=3.7" summary = "The official Python library for the groq API" groups = ["default"] @@ -762,8 +763,8 @@ dependencies = [ "typing-extensions<5,>=4.7", ] files = [ - {file = "groq-0.5.0-py3-none-any.whl", hash = "sha256:a7e6be1118bcdfea3ed071ec00f505a34d4e6ec28c435adb5a5afd33545683a1"}, - {file = "groq-0.5.0.tar.gz", hash = "sha256:d476cdc3383b45d2a4dc1876142a9542e663ea1029f9e07a05de24f895cae48c"}, + {file = "groq-0.8.0-py3-none-any.whl", hash = "sha256:f5e4e892d45001241a930db451e633ca1f0007e3f749deaa5d7360062fcd61e3"}, + {file = "groq-0.8.0.tar.gz", hash = "sha256:37ceb2f706bd516d0bfcac8e89048a24b375172987a0d6bd9efb521c54f6deff"}, ] [[package]] @@ -963,6 +964,64 @@ files = [ {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] +[[package]] +name = "jiter" +version = "0.4.0" +requires_python = ">=3.8" +summary = "Fast iterable JSON parser." +groups = ["default"] +files = [ + {file = "jiter-0.4.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4aa6226d82a4a4505078c0bd5947bad65399635fc5cd4b226512e41753624edf"}, + {file = "jiter-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:947111ac906740a948e7b63799481acd3d5ef666ccb178d146e25718640b7408"}, + {file = "jiter-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69572ffb4e84ae289a7422b9af4ea123cae2ce0772228859b37d4b26b4bc92ea"}, + {file = "jiter-0.4.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba6046cbb5d1baa5a781b846f7e5438596a332f249a857d63f86ef5d1d9563b0"}, + {file = "jiter-0.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4f346e54602782e66d07df0d1c7389384fd93680052ed6170da2c6dc758409e"}, + {file = "jiter-0.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49110ce693f07e97d61089d894cea05a0b9894d5ccc6ac6fc583028726c8c8af"}, + {file = "jiter-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e358df6fd129f3a4e087539f086355ad0107e5da16dbc8bc857d94222eaeed5"}, + {file = "jiter-0.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb852ca39a48f3c049def56f0d1771b32e948e4f429a782d14ef4cc64cfd26e"}, + {file = "jiter-0.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:44dc045decb2545bffe2da04ea4c36d9438d3f3d49fc47ed423ea75c352b712e"}, + {file = "jiter-0.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:413adb15372ac63db04373240f40925788e4282c997eeafc2040530049a0a599"}, + {file = "jiter-0.4.0-cp310-none-win32.whl", hash = "sha256:0b48ea71673a97b897e4b94bbc871e62495a5a85f836c9f90712a4c70aa3ef7e"}, + {file = "jiter-0.4.0-cp310-none-win_amd64.whl", hash = "sha256:6a1c84b44afafaf0ba6223679cf17af664b889da14da31d8af3595fd977d96fa"}, + {file = "jiter-0.4.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b2cc498345fa37ca23fbc20271a553aa46e6eb00924600f49b7dc4b2aa8952ee"}, + {file = "jiter-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:69f7221ac09ab421abf04f89942026868297c568133998fb181bcf435760cbf3"}, + {file = "jiter-0.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef7d01c52f3e5a56ae73af36bd13797dd1a56711eb522748e5e84d15425b3f10"}, + {file = "jiter-0.4.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:39be97d5ce0c4d0dae28c23c03a0af0501a725589427e99763f99c42e18aa402"}, + {file = "jiter-0.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eac2ed1ec1e577b92b7ea2d4e6de8aec0c1164defd8af8affdc8ec0f0ec2904a"}, + {file = "jiter-0.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6258837d184c92c9cb91c983c310ad7269d41afb49d34f00ca9246e073943a03"}, + {file = "jiter-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123c2a77b066bf17a4d021e238e8351058cfa56b90ac04f2522d120dc64ea055"}, + {file = "jiter-0.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2df939f792c7a40e55f36700417db551b9f6b84d348990fa0f2c608adeb1f11b"}, + {file = "jiter-0.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cb1b09b16d40cf9ba1d11ba11e5b96ad29286a6a1c4ad5e6a2aef5e352a89f5d"}, + {file = "jiter-0.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0efb4208889ebdbf933bf08dbcbc16e64ffd34c8e2b28044ee142789a9dc3a67"}, + {file = "jiter-0.4.0-cp311-none-win32.whl", hash = "sha256:20545ac1b68e7e5b066a1e8347840c9cebdd02ace65faae2e655fc02ec5c915c"}, + {file = "jiter-0.4.0-cp311-none-win_amd64.whl", hash = "sha256:6b300f9887c8e4431cd03a974ea3e4f9958885636003c3864220a9b2d2f8462b"}, + {file = "jiter-0.4.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:923432a0563bbae404ff25bb010e348514a69bfab979f2f8119b23b625dbf6d9"}, + {file = "jiter-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab8bb0ec8b97cec4422dc8b37b525442d969244488c805b834609ab0ccd788e2"}, + {file = "jiter-0.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b857adb127b9c533907226791eafa79c5038c3eb5a477984994bf7c4715ba518"}, + {file = "jiter-0.4.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2609cc0d1d8d470e921ff9a604afeb4c701bbe13e00bd9834d5aa6e7ea732a9b"}, + {file = "jiter-0.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d39e99f8b7df46a119b6f84321f6ba01f16fa46abfa765d44c05c486d8e66829"}, + {file = "jiter-0.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:56de8b518ebfe76a70f856741f6de248ce396c50a87acef827b6e8388e3a502d"}, + {file = "jiter-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488b7e777be47f67ce1a1f8f8eb907f9bbd81af5c03784a9bab09d025c250233"}, + {file = "jiter-0.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ea35e0ecbb5dadd457855eb980dcc548c14cf5341bcd22a43814cb56f2bcc79"}, + {file = "jiter-0.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e1a9e9ee69c80b63951c93226b68d0e955953f64fe758bad2afe7ef7f9016af9"}, + {file = "jiter-0.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:78e2f3cc2a32a21d43ccc5afcf66f5d17e827ccc4e6d21c0b353bdad2c7dcc9c"}, + {file = "jiter-0.4.0-cp312-none-win32.whl", hash = "sha256:eeaa7a2b47a99f4ebbb4142bb58b95617e09f24c87570f6a57d2770687c9ddbe"}, + {file = "jiter-0.4.0-cp312-none-win_amd64.whl", hash = "sha256:8d4a78b385b93ff59a67215d26000fcb4789a388fca3730d1b60fab17fc81e3c"}, + {file = "jiter-0.4.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:091e978f4e586a2f1c69bf940d45f4e6a23455877172a0ab7d6de04a3b119299"}, + {file = "jiter-0.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79134b2d601309bcbe3304a262d7d228ad61d53c80883231c637773000a6d683"}, + {file = "jiter-0.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c471473e0b05058b5d729ff04271b6d45a575ac8bd9948563268c734b380ac7e"}, + {file = "jiter-0.4.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb84b8930da8b32b0b1fdff9817e2c4b47e8981b5647ad11c4975403416e4112"}, + {file = "jiter-0.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f2805e28941751ebfe0948596a64cde4cfb9b84bea5282affd020063e659c96"}, + {file = "jiter-0.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42ef59f9e513bf081a8b5c5578933ea9c3a63e559e6e3501a3e72edcd456ff5e"}, + {file = "jiter-0.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae12e3906f9e565120ab569de261b738e3a1ec50c40e30c67499e4f893e9a8c"}, + {file = "jiter-0.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:264dc1324f45a793bc89af4f653225229eb17bca9ec7107dce6c8fb4fe68d20f"}, + {file = "jiter-0.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9a1c172ec47d846e25881dfbd52438ddb690da4ea04d185e477abd3db6c32f8a"}, + {file = "jiter-0.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ccde31d0bc114aedad0dbd71b7f63ba0f0eecd7ec9ae1926a0ca01c1eb2854e7"}, + {file = "jiter-0.4.0-cp39-none-win32.whl", hash = "sha256:13139b05792fbc13a0f9a5b4c89823ea0874141decae1b8f693f12bb1d28e061"}, + {file = "jiter-0.4.0-cp39-none-win_amd64.whl", hash = "sha256:3a729b2631c6d5551a41069697415fee9659c3eadc9ab87369376ba51930cd00"}, + {file = "jiter-0.4.0.tar.gz", hash = "sha256:68203e02e0419bc3eca717c580c2d8f615aeee1150e2a1fb68d6600a7e52a37c"}, +] + [[package]] name = "langdetect" version = "1.0.9" @@ -977,7 +1036,7 @@ files = [ [[package]] name = "litellm" -version = "1.35.38" +version = "1.38.10" requires_python = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" summary = "Library to easily interface with LLM API providers" groups = ["default"] @@ -986,15 +1045,15 @@ dependencies = [ "click", "importlib-metadata>=6.8.0", "jinja2<4.0.0,>=3.1.2", - "openai>=1.0.0", + "openai>=1.27.0", "python-dotenv>=0.2.0", "requests<3.0.0,>=2.31.0", "tiktoken>=0.4.0", "tokenizers", ] files = [ - {file = "litellm-1.35.38-py3-none-any.whl", hash = "sha256:79ab3403c945b340a751d889cf49030fee050487dff6294a21fb9586c49e3faf"}, - {file = "litellm-1.35.38.tar.gz", hash = "sha256:1a0b195c74d45ba0c2391c5be533c211ee1bcdba6be09e6950037432f62f79ea"}, + {file = "litellm-1.38.10-py3-none-any.whl", hash = "sha256:4d33465eacde566832b9d7aa7677476e61aa7ba4ec26631fb1c8411c87219ed1"}, + {file = "litellm-1.38.10.tar.gz", hash = "sha256:1a0b3088fe4b072f367343a7d7d25e4c5f9990975d9ee7dbf21f3b25ff046bb0"}, ] [[package]] @@ -1263,7 +1322,7 @@ files = [ [[package]] name = "openai" -version = "1.25.2" +version = "1.30.3" requires_python = ">=3.7.1" summary = "The official Python library for the openai API" groups = ["default"] @@ -1277,8 +1336,8 @@ dependencies = [ "typing-extensions<5,>=4.7", ] files = [ - {file = "openai-1.25.2-py3-none-any.whl", hash = "sha256:8df66384343e81ae49f5b9ee7d2d67df3b98d578d1e3a50994111e1c6062bf5e"}, - {file = "openai-1.25.2.tar.gz", hash = "sha256:a2b4a6ae9afb8b5578b5b69315ecc6c01564d4c8dbe18fe2375bb9547ff5038c"}, + {file = "openai-1.30.3-py3-none-any.whl", hash = "sha256:f88119c8a848998be533c71ab8aa832446fa72b7ddbc70917c3f5886dc132051"}, + {file = "openai-1.30.3.tar.gz", hash = "sha256:8e1bcdca2b96fe3636ab522fa153d88efde1b702d12ec32f1c73e9553ff93f45"}, ] [[package]] @@ -1618,8 +1677,8 @@ files = [ [[package]] name = "requests" -version = "2.31.0" -requires_python = ">=3.7" +version = "2.32.2" +requires_python = ">=3.8" summary = "Python HTTP for Humans." groups = ["default"] dependencies = [ @@ -1629,8 +1688,8 @@ dependencies = [ "urllib3<3,>=1.21.1", ] files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"}, + {file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"}, ] [[package]] @@ -1708,7 +1767,7 @@ files = [ [[package]] name = "tiktoken" -version = "0.6.0" +version = "0.7.0" requires_python = ">=3.8" summary = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" groups = ["default"] @@ -1717,35 +1776,35 @@ dependencies = [ "requests>=2.26.0", ] files = [ - {file = "tiktoken-0.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:277de84ccd8fa12730a6b4067456e5cf72fef6300bea61d506c09e45658d41ac"}, - {file = "tiktoken-0.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c44433f658064463650d61387623735641dcc4b6c999ca30bc0f8ba3fccaf5c"}, - {file = "tiktoken-0.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afb9a2a866ae6eef1995ab656744287a5ac95acc7e0491c33fad54d053288ad3"}, - {file = "tiktoken-0.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c62c05b3109fefca26fedb2820452a050074ad8e5ad9803f4652977778177d9f"}, - {file = "tiktoken-0.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ef917fad0bccda07bfbad835525bbed5f3ab97a8a3e66526e48cdc3e7beacf7"}, - {file = "tiktoken-0.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e095131ab6092d0769a2fda85aa260c7c383072daec599ba9d8b149d2a3f4d8b"}, - {file = "tiktoken-0.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:05b344c61779f815038292a19a0c6eb7098b63c8f865ff205abb9ea1b656030e"}, - {file = "tiktoken-0.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cefb9870fb55dca9e450e54dbf61f904aab9180ff6fe568b61f4db9564e78871"}, - {file = "tiktoken-0.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:702950d33d8cabc039845674107d2e6dcabbbb0990ef350f640661368df481bb"}, - {file = "tiktoken-0.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d49d076058f23254f2aff9af603863c5c5f9ab095bc896bceed04f8f0b013a"}, - {file = "tiktoken-0.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:430bc4e650a2d23a789dc2cdca3b9e5e7eb3cd3935168d97d43518cbb1f9a911"}, - {file = "tiktoken-0.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:293cb8669757301a3019a12d6770bd55bec38a4d3ee9978ddbe599d68976aca7"}, - {file = "tiktoken-0.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7bd1a288b7903aadc054b0e16ea78e3171f70b670e7372432298c686ebf9dd47"}, - {file = "tiktoken-0.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac76e000183e3b749634968a45c7169b351e99936ef46f0d2353cd0d46c3118d"}, - {file = "tiktoken-0.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17cc8a4a3245ab7d935c83a2db6bb71619099d7284b884f4b2aea4c74f2f83e3"}, - {file = "tiktoken-0.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:284aebcccffe1bba0d6571651317df6a5b376ff6cfed5aeb800c55df44c78177"}, - {file = "tiktoken-0.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c1a3a5d33846f8cd9dd3b7897c1d45722f48625a587f8e6f3d3e85080559be8"}, - {file = "tiktoken-0.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6318b2bb2337f38ee954fd5efa82632c6e5ced1d52a671370fa4b2eff1355e91"}, - {file = "tiktoken-0.6.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f5f0f2ed67ba16373f9a6013b68da298096b27cd4e1cf276d2d3868b5c7efd1"}, - {file = "tiktoken-0.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:75af4c0b16609c2ad02581f3cdcd1fb698c7565091370bf6c0cf8624ffaba6dc"}, - {file = "tiktoken-0.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:45577faf9a9d383b8fd683e313cf6df88b6076c034f0a16da243bb1c139340c3"}, - {file = "tiktoken-0.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:47fdcfe11bd55376785a6aea8ad1db967db7f66ea81aed5c43fad497521819a4"}, - {file = "tiktoken-0.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fb7d2ccbf1a7784810aff6b80b4012fb42c6fc37eaa68cb3b553801a5cc2d1fc"}, - {file = "tiktoken-0.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ccb7a111ee76af5d876a729a347f8747d5ad548e1487eeea90eaf58894b3138"}, - {file = "tiktoken-0.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2048e1086b48e3c8c6e2ceeac866561374cd57a84622fa49a6b245ffecb7744"}, - {file = "tiktoken-0.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07f229a5eb250b6403a61200199cecf0aac4aa23c3ecc1c11c1ca002cbb8f159"}, - {file = "tiktoken-0.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:432aa3be8436177b0db5a2b3e7cc28fd6c693f783b2f8722539ba16a867d0c6a"}, - {file = "tiktoken-0.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:8bfe8a19c8b5c40d121ee7938cd9c6a278e5b97dc035fd61714b4f0399d2f7a1"}, - {file = "tiktoken-0.6.0.tar.gz", hash = "sha256:ace62a4ede83c75b0374a2ddfa4b76903cf483e9cb06247f566be3bf14e6beed"}, + {file = "tiktoken-0.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485f3cc6aba7c6b6ce388ba634fbba656d9ee27f766216f45146beb4ac18b25f"}, + {file = "tiktoken-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e54be9a2cd2f6d6ffa3517b064983fb695c9a9d8aa7d574d1ef3c3f931a99225"}, + {file = "tiktoken-0.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79383a6e2c654c6040e5f8506f3750db9ddd71b550c724e673203b4f6b4b4590"}, + {file = "tiktoken-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d4511c52caacf3c4981d1ae2df85908bd31853f33d30b345c8b6830763f769c"}, + {file = "tiktoken-0.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13c94efacdd3de9aff824a788353aa5749c0faee1fbe3816df365ea450b82311"}, + {file = "tiktoken-0.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8e58c7eb29d2ab35a7a8929cbeea60216a4ccdf42efa8974d8e176d50c9a3df5"}, + {file = "tiktoken-0.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:21a20c3bd1dd3e55b91c1331bf25f4af522c525e771691adbc9a69336fa7f702"}, + {file = "tiktoken-0.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:10c7674f81e6e350fcbed7c09a65bca9356eaab27fb2dac65a1e440f2bcfe30f"}, + {file = "tiktoken-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:084cec29713bc9d4189a937f8a35dbdfa785bd1235a34c1124fe2323821ee93f"}, + {file = "tiktoken-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:811229fde1652fedcca7c6dfe76724d0908775b353556d8a71ed74d866f73f7b"}, + {file = "tiktoken-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b6e7dc2e7ad1b3757e8a24597415bafcfb454cebf9a33a01f2e6ba2e663992"}, + {file = "tiktoken-0.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1063c5748be36344c7e18c7913c53e2cca116764c2080177e57d62c7ad4576d1"}, + {file = "tiktoken-0.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20295d21419bfcca092644f7e2f2138ff947a6eb8cfc732c09cc7d76988d4a89"}, + {file = "tiktoken-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:959d993749b083acc57a317cbc643fb85c014d055b2119b739487288f4e5d1cb"}, + {file = "tiktoken-0.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:71c55d066388c55a9c00f61d2c456a6086673ab7dec22dd739c23f77195b1908"}, + {file = "tiktoken-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09ed925bccaa8043e34c519fbb2f99110bd07c6fd67714793c21ac298e449410"}, + {file = "tiktoken-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03c6c40ff1db0f48a7b4d2dafeae73a5607aacb472fa11f125e7baf9dce73704"}, + {file = "tiktoken-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20b5c6af30e621b4aca094ee61777a44118f52d886dbe4f02b70dfe05c15350"}, + {file = "tiktoken-0.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d427614c3e074004efa2f2411e16c826f9df427d3c70a54725cae860f09e4bf4"}, + {file = "tiktoken-0.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c46d7af7b8c6987fac9b9f61041b452afe92eb087d29c9ce54951280f899a97"}, + {file = "tiktoken-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0bc603c30b9e371e7c4c7935aba02af5994a909fc3c0fe66e7004070858d3f8f"}, + {file = "tiktoken-0.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cabc6dc77460df44ec5b879e68692c63551ae4fae7460dd4ff17181df75f1db7"}, + {file = "tiktoken-0.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8d57f29171255f74c0aeacd0651e29aa47dff6f070cb9f35ebc14c82278f3b25"}, + {file = "tiktoken-0.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ee92776fdbb3efa02a83f968c19d4997a55c8e9ce7be821ceee04a1d1ee149c"}, + {file = "tiktoken-0.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e215292e99cb41fbc96988ef62ea63bb0ce1e15f2c147a61acc319f8b4cbe5bf"}, + {file = "tiktoken-0.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a81bac94769cab437dd3ab0b8a4bc4e0f9cf6835bcaa88de71f39af1791727a"}, + {file = "tiktoken-0.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d6d73ea93e91d5ca771256dfc9d1d29f5a554b83821a1dc0891987636e0ae226"}, + {file = "tiktoken-0.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:2bcb28ddf79ffa424f171dfeef9a4daff61a94c631ca6813f43967cb263b83b9"}, + {file = "tiktoken-0.7.0.tar.gz", hash = "sha256:1077266e949c24e0291f6c350433c6f0971365ece2b173a23bc3b9f9defef6b6"}, ] [[package]] diff --git a/requirements.txt b/requirements.txt index fd9715a..d8b6382 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ aiohttp==3.9.5 aiosignal==1.3.1 annotated-types==0.6.0 -anthropic==0.25.7 +anthropic==0.26.1 anyio==4.3.0 async-timeout==4.0.3; python_version < "3.11" attrs==23.2.0 @@ -25,14 +25,14 @@ exceptiongroup==1.2.1; python_version < "3.11" filelock==3.14.0 frozenlist==1.4.1 fsspec==2024.3.1 -google-ai-generativelanguage==0.6.2 -google-api-core==2.19.0 -google-api-python-client==2.127.0 -google-auth==2.29.0 +google-ai-generativelanguage==0.6.10 +google-api-core==2.21.0 +google-api-python-client==2.149.0 +google-auth==2.35.0 google-auth-httplib2==0.2.0 -google-generativeai==0.5.2 -googleapis-common-protos==1.63.0 -groq==0.5.0 +google-generativeai==0.8.3 +googleapis-common-protos==1.65.0 +groq==0.8.0 grpcio==1.63.0 grpcio-status==1.62.2 h11==0.14.0 @@ -43,14 +43,15 @@ huggingface-hub==0.22.2 idna==3.7 importlib-metadata==7.1.0 jinja2==3.1.3 +jiter==0.4.0 langdetect==1.0.9 -litellm==1.35.38 +litellm==1.38.10 lxml==5.2.1 markdown-it-py==3.0.0 markupsafe==2.1.5 mdurl==0.1.2 multidict==6.0.5 -openai==1.25.2 +openai==1.30.3 packaging==24.0 proto-plus==1.23.0 protobuf==4.25.3 @@ -65,14 +66,14 @@ pyparsing==3.1.2; python_version > "3.0" python-dotenv==1.0.1 pyyaml==6.0.1 regex==2024.4.28 -requests==2.31.0 +requests==2.32.2 rich==13.7.1 rsa==4.9 six==1.16.0 sniffio==1.3.1 socksio==1.0.0 soupsieve==2.5 -tiktoken==0.6.0 +tiktoken==0.7.0 tokenizers==0.19.1 tqdm==4.66.4 typing-extensions==4.11.0