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