Intoval/privateChatGPT
1
1from __future__ import annotations2from typing import TYPE_CHECKING, List3 4import logging5import json6import commentjson as cjson7import os8import sys9import requests10import urllib311import platform12 13from tqdm import tqdm14import colorama15from duckduckgo_search import ddg16import asyncio17import aiohttp18from enum import Enum19import uuid20 21from .presets import *22from .llama_func import *23from .utils import *24from . import shared25from .config import retrieve_proxy26from modules import config27from .base_model import BaseLLMModel, ModelType28 29 30class OpenAIClient(BaseLLMModel):31 def __init__(32 self,33 model_name,34 api_key,35 system_prompt=INITIAL_SYSTEM_PROMPT,36 temperature=1.0,37 top_p=1.0,38 ) -> None:39 super().__init__(40 model_name=model_name,41 temperature=temperature,42 top_p=top_p,43 system_prompt=system_prompt,44 )45 self.api_key = api_key46 self.need_api_key = True47 self._refresh_header()48 49 def get_answer_stream_iter(self):50 response = self._get_response(stream=True)51 if response is not None:52 iter = self._decode_chat_response(response)53 partial_text = ""54 for i in iter:55 partial_text += i56 yield partial_text57 else:58 yield STANDARD_ERROR_MSG + GENERAL_ERROR_MSG59 60 def get_answer_at_once(self):61 response = self._get_response()62 response = json.loads(response.text)63 content = response["choices"][0]["message"]["content"]64 total_token_count = response["usage"]["total_tokens"]65 return content, total_token_count66 67 def count_token(self, user_input):68 input_token_count = count_token(construct_user(user_input))69 if self.system_prompt is not None and len(self.all_token_counts) == 0:70 system_prompt_token_count = count_token(71 construct_system(self.system_prompt)72 )73 return input_token_count + system_prompt_token_count74 return input_token_count75 76 def billing_info(self):77 try:78 curr_time = datetime.datetime.now()79 last_day_of_month = get_last_day_of_month(80 curr_time).strftime("%Y-%m-%d")81 first_day_of_month = curr_time.replace(day=1).strftime("%Y-%m-%d")82 usage_url = f"{shared.state.usage_api_url}?start_date={first_day_of_month}&end_date={last_day_of_month}"83 try:84 usage_data = self._get_billing_data(usage_url)85 except Exception as e:86 logging.error(f"获取API使用情况失败:" + str(e))87 return i18n("**获取API使用情况失败**")88 rounded_usage = "{:.5f}".format(usage_data["total_usage"] / 100)89 return i18n("**本月使用金额** ") + f"\u3000 ${rounded_usage}"90 except requests.exceptions.ConnectTimeout:91 status_text = (92 STANDARD_ERROR_MSG + CONNECTION_TIMEOUT_MSG + ERROR_RETRIEVE_MSG93 )94 return status_text95 except requests.exceptions.ReadTimeout:96 status_text = STANDARD_ERROR_MSG + READ_TIMEOUT_MSG + ERROR_RETRIEVE_MSG97 return status_text98 except Exception as e:99 logging.error(i18n("获取API使用情况失败:") + str(e))100 return STANDARD_ERROR_MSG + ERROR_RETRIEVE_MSG101 102 def set_token_upper_limit(self, new_upper_limit):103 pass104 105 @shared.state.switching_api_key # 在不开启多账号模式的时候,这个装饰器不会起作用106 def _get_response(self, stream=False):107 openai_api_key = self.api_key108 system_prompt = self.system_prompt109 history = self.history110 logging.debug(colorama.Fore.YELLOW +111 f"{history}" + colorama.Fore.RESET)112 headers = {113 "Content-Type": "application/json",114 "Authorization": f"Bearer {openai_api_key}",115 }116 117 if system_prompt is not None:118 history = [construct_system(system_prompt), *history]119 120 payload = {121 "model": self.model_name,122 "messages": history,123 "temperature": self.temperature,124 "top_p": self.top_p,125 "n": self.n_choices,126 "stream": stream,127 "presence_penalty": self.presence_penalty,128 "frequency_penalty": self.frequency_penalty,129 }130 131 if self.max_generation_token is not None:132 payload["max_tokens"] = self.max_generation_token133 if self.stop_sequence is not None:134 payload["stop"] = self.stop_sequence135 if self.logit_bias is not None:136 payload["logit_bias"] = self.logit_bias137 if self.user_identifier is not None:138 payload["user"] = self.user_identifier139 140 if stream:141 timeout = TIMEOUT_STREAMING142 else:143 timeout = TIMEOUT_ALL144 145 # 如果有自定义的api-host,使用自定义host发送请求,否则使用默认设置发送请求146 if shared.state.completion_url != COMPLETION_URL:147 logging.info(f"使用自定义API URL: {shared.state.completion_url}")148 149 with retrieve_proxy():150 try:151 response = requests.post(152 shared.state.completion_url,153 headers=headers,154 json=payload,155 stream=stream,156 timeout=timeout,157 )158 except:159 return None160 return response161 162 def _refresh_header(self):163 self.headers = {164 "Content-Type": "application/json",165 "Authorization": f"Bearer {self.api_key}",166 }167 168 def _get_billing_data(self, billing_url):169 with retrieve_proxy():170 response = requests.get(171 billing_url,172 headers=self.headers,173 timeout=TIMEOUT_ALL,174 )175 176 if response.status_code == 200:177 data = response.json()178 return data179 else:180 raise Exception(181 f"API request failed with status code {response.status_code}: {response.text}"182 )183 184 def _decode_chat_response(self, response):185 error_msg = ""186 for chunk in response.iter_lines():187 if chunk:188 chunk = chunk.decode()189 chunk_length = len(chunk)190 try:191 chunk = json.loads(chunk[6:])192 except json.JSONDecodeError:193 print(i18n("JSON解析错误,收到的内容: ") + f"{chunk}")194 error_msg += chunk195 continue196 if chunk_length > 6 and "delta" in chunk["choices"][0]:197 if chunk["choices"][0]["finish_reason"] == "stop":198 break199 try:200 yield chunk["choices"][0]["delta"]["content"]201 except Exception as e:202 # logging.error(f"Error: {e}")203 continue204 if error_msg:205 raise Exception(error_msg)206 207 208class ChatGLM_Client(BaseLLMModel):209 def __init__(self, model_name) -> None:210 super().__init__(model_name=model_name)211 from transformers import AutoTokenizer, AutoModel212 import torch213 global CHATGLM_TOKENIZER, CHATGLM_MODEL214 if CHATGLM_TOKENIZER is None or CHATGLM_MODEL is None:215 system_name = platform.system()216 model_path = None217 if os.path.exists("models"):218 model_dirs = os.listdir("models")219 if model_name in model_dirs:220 model_path = f"models/{model_name}"221 if model_path is not None:222 model_source = model_path223 else:224 model_source = f"THUDM/{model_name}"225 CHATGLM_TOKENIZER = AutoTokenizer.from_pretrained(226 model_source, trust_remote_code=True227 )228 quantified = False229 if "int4" in model_name:230 quantified = True231 model = AutoModel.from_pretrained(232 model_source, trust_remote_code=True233 )234 if torch.cuda.is_available():235 # run on CUDA236 logging.info("CUDA is available, using CUDA")237 model = model.half().cuda()238 # mps加速还存在一些问题,暂时不使用239 elif system_name == "Darwin" and model_path is not None and not quantified:240 logging.info("Running on macOS, using MPS")241 # running on macOS and model already downloaded242 model = model.half().to("mps")243 else:244 logging.info("GPU is not available, using CPU")245 model = model.float()246 model = model.eval()247 CHATGLM_MODEL = model248 249 def _get_glm_style_input(self):250 history = [x["content"] for x in self.history]251 query = history.pop()252 logging.debug(colorama.Fore.YELLOW +253 f"{history}" + colorama.Fore.RESET)254 assert (255 len(history) % 2 == 0256 ), f"History should be even length. current history is: {history}"257 history = [[history[i], history[i + 1]]258 for i in range(0, len(history), 2)]259 return history, query260 261 def get_answer_at_once(self):262 history, query = self._get_glm_style_input()263 response, _ = CHATGLM_MODEL.chat(264 CHATGLM_TOKENIZER, query, history=history)265 return response, len(response)266 267 def get_answer_stream_iter(self):268 history, query = self._get_glm_style_input()269 for response, history in CHATGLM_MODEL.stream_chat(270 CHATGLM_TOKENIZER,271 query,272 history,273 max_length=self.token_upper_limit,274 top_p=self.top_p,275 temperature=self.temperature,276 ):277 yield response278 279 280class LLaMA_Client(BaseLLMModel):281 def __init__(282 self,283 model_name,284 lora_path=None,285 ) -> None:286 super().__init__(model_name=model_name)287 from lmflow.datasets.dataset import Dataset288 from lmflow.pipeline.auto_pipeline import AutoPipeline289 from lmflow.models.auto_model import AutoModel290 from lmflow.args import ModelArguments, DatasetArguments, InferencerArguments291 292 self.max_generation_token = 1000293 self.end_string = "\n\n"294 # We don't need input data295 data_args = DatasetArguments(dataset_path=None)296 self.dataset = Dataset(data_args)297 self.system_prompt = ""298 299 global LLAMA_MODEL, LLAMA_INFERENCER300 if LLAMA_MODEL is None or LLAMA_INFERENCER is None:301 model_path = None302 if os.path.exists("models"):303 model_dirs = os.listdir("models")304 if model_name in model_dirs:305 model_path = f"models/{model_name}"306 if model_path is not None:307 model_source = model_path308 else:309 model_source = f"decapoda-research/{model_name}"310 # raise Exception(f"models目录下没有这个模型: {model_name}")311 if lora_path is not None:312 lora_path = f"lora/{lora_path}"313 model_args = ModelArguments(model_name_or_path=model_source, lora_model_path=lora_path, model_type=None, config_overrides=None, config_name=None, tokenizer_name=None, cache_dir=None,314 use_fast_tokenizer=True, model_revision='main', use_auth_token=False, torch_dtype=None, use_lora=False, lora_r=8, lora_alpha=32, lora_dropout=0.1, use_ram_optimized_load=True)315 pipeline_args = InferencerArguments(316 local_rank=0, random_seed=1, deepspeed='configs/ds_config_chatbot.json', mixed_precision='bf16')317 318 with open(pipeline_args.deepspeed, "r") as f:319 ds_config = json.load(f)320 LLAMA_MODEL = AutoModel.get_model(321 model_args,322 tune_strategy="none",323 ds_config=ds_config,324 )325 LLAMA_INFERENCER = AutoPipeline.get_pipeline(326 pipeline_name="inferencer",327 model_args=model_args,328 data_args=data_args,329 pipeline_args=pipeline_args,330 )331 # Chats332 # model_name = model_args.model_name_or_path333 # if model_args.lora_model_path is not None:334 # model_name += f" + {model_args.lora_model_path}"335 336 # context = (337 # "You are a helpful assistant who follows the given instructions"338 # " unconditionally."339 # )340 341 def _get_llama_style_input(self):342 history = []343 instruction = ""344 if self.system_prompt:345 instruction = (f"Instruction: {self.system_prompt}\n")346 for x in self.history:347 if x["role"] == "user":348 history.append(f"{instruction}Input: {x['content']}")349 else:350 history.append(f"Output: {x['content']}")351 context = "\n\n".join(history)352 context += "\n\nOutput: "353 return context354 355 def get_answer_at_once(self):356 context = self._get_llama_style_input()357 358 input_dataset = self.dataset.from_dict(359 {"type": "text_only", "instances": [{"text": context}]}360 )361 362 output_dataset = LLAMA_INFERENCER.inference(363 model=LLAMA_MODEL,364 dataset=input_dataset,365 max_new_tokens=self.max_generation_token,366 temperature=self.temperature,367 )368 369 response = output_dataset.to_dict()["instances"][0]["text"]370 return response, len(response)371 372 def get_answer_stream_iter(self):373 context = self._get_llama_style_input()374 partial_text = ""375 step = 1376 for _ in range(0, self.max_generation_token, step):377 input_dataset = self.dataset.from_dict(378 {"type": "text_only", "instances": [379 {"text": context + partial_text}]}380 )381 output_dataset = LLAMA_INFERENCER.inference(382 model=LLAMA_MODEL,383 dataset=input_dataset,384 max_new_tokens=step,385 temperature=self.temperature,386 )387 response = output_dataset.to_dict()["instances"][0]["text"]388 if response == "" or response == self.end_string:389 break390 partial_text += response391 yield partial_text392 393 394class XMBot_Client(BaseLLMModel):395 def __init__(self, api_key):396 super().__init__(model_name="xmchat")397 self.api_key = api_key398 self.session_id = None399 self.reset()400 self.image_bytes = None401 self.image_path = None402 self.xm_history = []403 self.url = "https://xmbot.net/web"404 405 def reset(self):406 self.session_id = str(uuid.uuid4())407 return [], "已重置"408 409 def try_read_image(self, filepath):410 import base64411 412 def is_image_file(filepath):413 # 判断文件是否为图片414 valid_image_extensions = [".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff"]415 file_extension = os.path.splitext(filepath)[1].lower()416 return file_extension in valid_image_extensions417 418 def read_image_as_bytes(filepath):419 # 读取图片文件并返回比特流420 with open(filepath, "rb") as f:421 image_bytes = f.read()422 return image_bytes423 424 if is_image_file(filepath):425 logging.info(f"读取图片文件: {filepath}")426 image_bytes = read_image_as_bytes(filepath)427 base64_encoded_image = base64.b64encode(image_bytes).decode()428 self.image_bytes = base64_encoded_image429 self.image_path = filepath430 else:431 self.image_bytes = None432 self.image_path = None433 434 def prepare_inputs(self, real_inputs, use_websearch, files, reply_language, chatbot):435 fake_inputs = real_inputs436 display_append = ""437 limited_context = False438 return limited_context, fake_inputs, display_append, real_inputs, chatbot439 440 def handle_file_upload(self, files, chatbot):441 """if the model accepts multi modal input, implement this function"""442 if files:443 for file in files:444 if file.name:445 logging.info(f"尝试读取图像: {file.name}")446 self.try_read_image(file.name)447 if self.image_path is not None:448 chatbot = chatbot + [((self.image_path,), None)]449 if self.image_bytes is not None:450 logging.info("使用图片作为输入")451 conv_id = str(uuid.uuid4())452 data = {453 "user_id": self.api_key,454 "session_id": self.session_id,455 "uuid": conv_id,456 "data_type": "imgbase64",457 "data": self.image_bytes458 }459 response = requests.post(self.url, json=data)460 response = json.loads(response.text)461 logging.info(f"图片回复: {response['data']}")462 return None, chatbot, None463 464 def get_answer_at_once(self):465 question = self.history[-1]["content"]466 conv_id = str(uuid.uuid4())467 data = {468 "user_id": self.api_key,469 "session_id": self.session_id,470 "uuid": conv_id,471 "data_type": "text",472 "data": question473 }474 response = requests.post(self.url, json=data)475 try:476 response = json.loads(response.text)477 return response["data"], len(response["data"])478 except Exception as e:479 return response.text, len(response.text)480 481 482 483 484def get_model(485 model_name,486 lora_model_path=None,487 access_key=None,488 temperature=None,489 top_p=None,490 system_prompt=None,491) -> BaseLLMModel:492 msg = i18n("模型设置为了:") + f" {model_name}"493 model_type = ModelType.get_type(model_name)494 lora_selector_visibility = False495 lora_choices = []496 dont_change_lora_selector = False497 if model_type != ModelType.OpenAI:498 config.local_embedding = True499 # del current_model.model500 model = None501 try:502 if model_type == ModelType.OpenAI:503 logging.info(f"正在加载OpenAI模型: {model_name}")504 model = OpenAIClient(505 model_name=model_name,506 api_key=access_key,507 system_prompt=system_prompt,508 temperature=temperature,509 top_p=top_p,510 )511 elif model_type == ModelType.ChatGLM:512 logging.info(f"正在加载ChatGLM模型: {model_name}")513 model = ChatGLM_Client(model_name)514 elif model_type == ModelType.LLaMA and lora_model_path == "":515 msg = f"现在请为 {model_name} 选择LoRA模型"516 logging.info(msg)517 lora_selector_visibility = True518 if os.path.isdir("lora"):519 lora_choices = get_file_names(520 "lora", plain=True, filetypes=[""])521 lora_choices = ["No LoRA"] + lora_choices522 elif model_type == ModelType.LLaMA and lora_model_path != "":523 logging.info(f"正在加载LLaMA模型: {model_name} + {lora_model_path}")524 dont_change_lora_selector = True525 if lora_model_path == "No LoRA":526 lora_model_path = None527 msg += " + No LoRA"528 else:529 msg += f" + {lora_model_path}"530 model = LLaMA_Client(model_name, lora_model_path)531 elif model_type == ModelType.XMBot:532 model = XMBot_Client(api_key=access_key)533 elif model_type == ModelType.Unknown:534 raise ValueError(f"未知模型: {model_name}")535 logging.info(msg)536 except Exception as e:537 logging.error(e)538 msg = f"{STANDARD_ERROR_MSG}: {e}"539 if dont_change_lora_selector:540 return model, msg541 else:542 return model, msg, gr.Dropdown.update(choices=lora_choices, visible=lora_selector_visibility)543 544 545if __name__ == "__main__":546 with open("config.json", "r") as f:547 openai_api_key = cjson.load(f)["openai_api_key"]548 # set logging level to debug549 logging.basicConfig(level=logging.DEBUG)550 # client = ModelManager(model_name="gpt-3.5-turbo", access_key=openai_api_key)551 client = get_model(model_name="chatglm-6b-int4")552 chatbot = []553 stream = False554 # 测试账单功能555 logging.info(colorama.Back.GREEN + "测试账单功能" + colorama.Back.RESET)556 logging.info(client.billing_info())557 # 测试问答558 logging.info(colorama.Back.GREEN + "测试问答" + colorama.Back.RESET)559 question = "巴黎是中国的首都吗?"560 for i in client.predict(inputs=question, chatbot=chatbot, stream=stream):561 logging.info(i)562 logging.info(f"测试问答后history : {client.history}")563 # 测试记忆力564 logging.info(colorama.Back.GREEN + "测试记忆力" + colorama.Back.RESET)565 question = "我刚刚问了你什么问题?"566 for i in client.predict(inputs=question, chatbot=chatbot, stream=stream):567 logging.info(i)568 logging.info(f"测试记忆力后history : {client.history}")569 # 测试重试功能570 logging.info(colorama.Back.GREEN + "测试重试功能" + colorama.Back.RESET)571 for i in client.retry(chatbot=chatbot, stream=stream):572 logging.info(i)573 logging.info(f"重试后history : {client.history}")574 # # 测试总结功能575 # print(colorama.Back.GREEN + "测试总结功能" + colorama.Back.RESET)576 # chatbot, msg = client.reduce_token_size(chatbot=chatbot)577 # print(chatbot, msg)578 # print(f"总结后history: {client.history}")579 