diff --git a/.gitignore b/.gitignore index 38ec6b5e4..e0911cf4a 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,5 @@ objdump* .DS_Store .aider* + +TODO \ No newline at end of file diff --git a/config.py b/config.py index f654054e2..6e636278e 100644 --- a/config.py +++ b/config.py @@ -36,8 +36,11 @@ "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-mini", "gpt-3.5-turbo-1106", "gpt-3.5-turbo-16k", "gpt-3.5-turbo", "azure-gpt-3.5", "gpt-4", "gpt-4-32k", "azure-gpt-4", "glm-4", "glm-4v", "glm-3-turbo", - "gemini-pro", "chatglm3" + "gemini-1.5-pro", "chatglm3" ] + +EMBEDDING_MODEL = "text-embedding-3-small" + # --- --- --- --- # P.S. 其他可用的模型还包括 # AVAIL_LLM_MODELS = [ @@ -50,6 +53,7 @@ # "claude-3-haiku-20240307","claude-3-sonnet-20240229","claude-3-opus-20240229", "claude-2.1", "claude-instant-1.2", # "moss", "llama2", "chatglm_onnx", "internlm", "jittorllms_pangualpha", "jittorllms_llama", # "deepseek-chat" ,"deepseek-coder", +# "gemini-1.5-flash", # "yi-34b-chat-0205","yi-34b-chat-200k","yi-large","yi-medium","yi-spark","yi-large-turbo","yi-large-preview", # ] # --- --- --- --- @@ -296,7 +300,7 @@ # 除了连接OpenAI之外,还有哪些场合允许使用代理,请尽量不要修改 WHEN_TO_USE_PROXY = ["Download_LLM", "Download_Gradio_Theme", "Connect_Grobid", - "Warmup_Modules", "Nougat_Download", "AutoGen"] + "Warmup_Modules", "Nougat_Download", "AutoGen", "Connect_OpenAI_Embedding"] # 启用插件热加载 diff --git a/crazy_functional.py b/crazy_functional.py index 222ca3253..1d1945098 100644 --- a/crazy_functional.py +++ b/crazy_functional.py @@ -5,6 +5,7 @@ def get_crazy_functions(): from crazy_functions.读文章写摘要 import 读文章写摘要 from crazy_functions.生成函数注释 import 批量生成函数注释 + from crazy_functions.Rag_Interface import Rag问答 from crazy_functions.SourceCode_Analyse import 解析项目本身 from crazy_functions.SourceCode_Analyse import 解析一个Python项目 from crazy_functions.SourceCode_Analyse import 解析一个Matlab项目 @@ -50,6 +51,13 @@ def get_crazy_functions(): from crazy_functions.SourceCode_Comment import 注释Python项目 function_plugins = { + "Rag智能召回": { + "Group": "对话", + "Color": "stop", + "AsButton": False, + "Info": "将问答数据记录到向量库中,作为长期参考。", + "Function": HotReload(Rag问答), + }, "虚空终端": { "Group": "对话|编程|学术|智能体", "Color": "stop", diff --git a/crazy_functions/Rag_Interface.py b/crazy_functions/Rag_Interface.py new file mode 100644 index 000000000..d83d8ca5a --- /dev/null +++ b/crazy_functions/Rag_Interface.py @@ -0,0 +1,95 @@ +from toolbox import CatchException, update_ui, get_conf, get_log_folder, update_ui_lastest_msg +from crazy_functions.crazy_utils import input_clipping +from crazy_functions.crazy_utils import request_gpt_model_in_new_thread_with_ui_alive + +VECTOR_STORE_TYPE = "Milvus" + +if VECTOR_STORE_TYPE == "Milvus": + try: + from crazy_functions.rag_fns.milvus_worker import MilvusRagWorker as LlamaIndexRagWorker + except: + VECTOR_STORE_TYPE = "Simple" + +if VECTOR_STORE_TYPE == "Simple": + from crazy_functions.rag_fns.llama_index_worker import LlamaIndexRagWorker + + +RAG_WORKER_REGISTER = {} + +MAX_HISTORY_ROUND = 5 +MAX_CONTEXT_TOKEN_LIMIT = 4096 +REMEMBER_PREVIEW = 1000 + +@CatchException +def Rag问答(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request): + + # 1. we retrieve rag worker from global context + user_name = chatbot.get_user() + checkpoint_dir = get_log_folder(user_name, plugin_name='experimental_rag') + if user_name in RAG_WORKER_REGISTER: + rag_worker = RAG_WORKER_REGISTER[user_name] + else: + rag_worker = RAG_WORKER_REGISTER[user_name] = LlamaIndexRagWorker( + user_name, + llm_kwargs, + checkpoint_dir=checkpoint_dir, + auto_load_checkpoint=True) + current_context = f"{VECTOR_STORE_TYPE} @ {checkpoint_dir}" + tip = "提示:输入“清空向量数据库”可以清空RAG向量数据库" + if txt == "清空向量数据库": + chatbot.append([txt, f'正在清空 ({current_context}) ...']) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 + rag_worker.purge() + yield from update_ui_lastest_msg('已清空', chatbot, history, delay=0) # 刷新界面 + return + + chatbot.append([txt, f'正在召回知识 ({current_context}) ...']) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 + + # 2. clip history to reduce token consumption + # 2-1. reduce chat round + txt_origin = txt + + if len(history) > MAX_HISTORY_ROUND * 2: + history = history[-(MAX_HISTORY_ROUND * 2):] + txt_clip, history, flags = input_clipping(txt, history, max_token_limit=MAX_CONTEXT_TOKEN_LIMIT, return_clip_flags=True) + input_is_clipped_flag = (flags["original_input_len"] != flags["clipped_input_len"]) + + # 2-2. if input is clipped, add input to vector store before retrieve + if input_is_clipped_flag: + yield from update_ui_lastest_msg('检测到长输入, 正在向量化 ...', chatbot, history, delay=0) # 刷新界面 + # save input to vector store + rag_worker.add_text_to_vector_store(txt_origin) + yield from update_ui_lastest_msg('向量化完成 ...', chatbot, history, delay=0) # 刷新界面 + if len(txt_origin) > REMEMBER_PREVIEW: + HALF = REMEMBER_PREVIEW//2 + i_say_to_remember = txt[:HALF] + f" ...\n...(省略{len(txt_origin)-REMEMBER_PREVIEW}字)...\n... " + txt[-HALF:] + if (flags["original_input_len"] - flags["clipped_input_len"]) > HALF: + txt_clip = txt_clip + f" ...\n...(省略{len(txt_origin)-len(txt_clip)-HALF}字)...\n... " + txt[-HALF:] + else: + pass + i_say = txt_clip + else: + i_say_to_remember = i_say = txt_clip + else: + i_say_to_remember = i_say = txt_clip + + # 3. we search vector store and build prompts + nodes = rag_worker.retrieve_from_store_with_query(i_say) + prompt = rag_worker.build_prompt(query=i_say, nodes=nodes) + + # 4. it is time to query llms + if len(chatbot) != 0: chatbot.pop(-1) # pop temp chat, because we are going to add them again inside `request_gpt_model_in_new_thread_with_ui_alive` + model_say = yield from request_gpt_model_in_new_thread_with_ui_alive( + inputs=prompt, inputs_show_user=i_say, + llm_kwargs=llm_kwargs, chatbot=chatbot, history=history, + sys_prompt=system_prompt, + retry_times_at_unknown_error=0 + ) + + # 5. remember what has been asked / answered + yield from update_ui_lastest_msg(model_say + '

' + f'对话记忆中, 请稍等 ({current_context}) ...', chatbot, history, delay=0.5) # 刷新界面 + rag_worker.remember_qa(i_say_to_remember, model_say) + history.extend([i_say, model_say]) + + yield from update_ui_lastest_msg(model_say, chatbot, history, delay=0, msg=tip) # 刷新界面 diff --git a/crazy_functions/Social_Helper.py b/crazy_functions/Social_Helper.py new file mode 100644 index 000000000..9627f9c03 --- /dev/null +++ b/crazy_functions/Social_Helper.py @@ -0,0 +1,65 @@ +from toolbox import CatchException, update_ui, get_conf, get_log_folder, update_ui_lastest_msg +from crazy_functions.crazy_utils import input_clipping +from crazy_functions.crazy_utils import request_gpt_model_in_new_thread_with_ui_alive +import pickle, os + +SOCIAL_NETWOK_WORKER_REGISTER = {} + +class SocialNetwork(): + def __init__(self): + self.people = [] + +class SocialNetworkWorker(): + def __init__(self, user_name, llm_kwargs, auto_load_checkpoint=True, checkpoint_dir=None) -> None: + self.user_name = user_name + self.checkpoint_dir = checkpoint_dir + if auto_load_checkpoint: + self.social_network = self.load_from_checkpoint(checkpoint_dir) + else: + self.social_network = SocialNetwork() + + def does_checkpoint_exist(self, checkpoint_dir=None): + import os, glob + if checkpoint_dir is None: checkpoint_dir = self.checkpoint_dir + if not os.path.exists(checkpoint_dir): return False + if len(glob.glob(os.path.join(checkpoint_dir, "social_network.pkl"))) == 0: return False + return True + + def save_to_checkpoint(self, checkpoint_dir=None): + if checkpoint_dir is None: checkpoint_dir = self.checkpoint_dir + with open(os.path.join(checkpoint_dir, 'social_network.pkl'), "wb+") as f: + pickle.dump(self.social_network, f) + return + + def load_from_checkpoint(self, checkpoint_dir=None): + if checkpoint_dir is None: checkpoint_dir = self.checkpoint_dir + if self.does_checkpoint_exist(checkpoint_dir=checkpoint_dir): + with open(os.path.join(checkpoint_dir, 'social_network.pkl'), "rb") as f: + social_network = pickle.load(f) + return social_network + else: + return SocialNetwork() + + +@CatchException +def I人助手(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request, num_day=5): + + # 1. we retrieve worker from global context + user_name = chatbot.get_user() + checkpoint_dir=get_log_folder(user_name, plugin_name='experimental_rag') + if user_name in SOCIAL_NETWOK_WORKER_REGISTER: + social_network_worker = SOCIAL_NETWOK_WORKER_REGISTER[user_name] + else: + social_network_worker = SOCIAL_NETWOK_WORKER_REGISTER[user_name] = SocialNetworkWorker( + user_name, + llm_kwargs, + checkpoint_dir=checkpoint_dir, + auto_load_checkpoint=True + ) + + # 2. save + social_network_worker.social_network.people.append("张三") + social_network_worker.save_to_checkpoint(checkpoint_dir) + chatbot.append(["good", "work"]) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 + diff --git a/crazy_functions/crazy_utils.py b/crazy_functions/crazy_utils.py index 0cccceebd..d2fe1d3ec 100644 --- a/crazy_functions/crazy_utils.py +++ b/crazy_functions/crazy_utils.py @@ -4,7 +4,7 @@ import os import logging -def input_clipping(inputs, history, max_token_limit): +def input_clipping(inputs, history, max_token_limit, return_clip_flags=False): """ 当输入文本 + 历史文本超出最大限制时,采取措施丢弃一部分文本。 输入: @@ -20,17 +20,20 @@ def input_clipping(inputs, history, max_token_limit): enc = model_info["gpt-3.5-turbo"]['tokenizer'] def get_token_num(txt): return len(enc.encode(txt, disallowed_special=())) + mode = 'input-and-history' # 当 输入部分的token占比 小于 全文的一半时,只裁剪历史 input_token_num = get_token_num(inputs) + original_input_len = len(inputs) if input_token_num < max_token_limit//2: mode = 'only-history' max_token_limit = max_token_limit - input_token_num everything = [inputs] if mode == 'input-and-history' else [''] everything.extend(history) - n_token = get_token_num('\n'.join(everything)) + full_token_num = n_token = get_token_num('\n'.join(everything)) everything_token = [get_token_num(e) for e in everything] + everything_token_num = sum(everything_token) delta = max(everything_token) // 16 # 截断时的颗粒度 while n_token > max_token_limit: @@ -43,10 +46,24 @@ def get_token_num(txt): return len(enc.encode(txt, disallowed_special=())) if mode == 'input-and-history': inputs = everything[0] + full_token_num = everything_token_num else: - pass + full_token_num = everything_token_num + input_token_num + history = everything[1:] - return inputs, history + + flags = { + "mode": mode, + "original_input_token_num": input_token_num, + "original_full_token_num": full_token_num, + "original_input_len": original_input_len, + "clipped_input_len": len(inputs), + } + + if not return_clip_flags: + return inputs, history + else: + return inputs, history, flags def request_gpt_model_in_new_thread_with_ui_alive( inputs, inputs_show_user, llm_kwargs, diff --git a/crazy_functions/rag_fns/llama_index_worker.py b/crazy_functions/rag_fns/llama_index_worker.py new file mode 100644 index 000000000..7a5599279 --- /dev/null +++ b/crazy_functions/rag_fns/llama_index_worker.py @@ -0,0 +1,129 @@ +import llama_index +import os +import atexit +from typing import List +from llama_index.core import Document +from llama_index.core.schema import TextNode +from request_llms.embed_models.openai_embed import OpenAiEmbeddingModel +from shared_utils.connect_void_terminal import get_chat_default_kwargs +from llama_index.core import VectorStoreIndex, SimpleDirectoryReader +from crazy_functions.rag_fns.vector_store_index import GptacVectorStoreIndex +from llama_index.core.ingestion import run_transformations +from llama_index.core import PromptTemplate +from llama_index.core.response_synthesizers import TreeSummarize + +DEFAULT_QUERY_GENERATION_PROMPT = """\ +Now, you have context information as below: +--------------------- +{context_str} +--------------------- +Answer the user request below (use the context information if necessary, otherwise you can ignore them): +--------------------- +{query_str} +""" + +QUESTION_ANSWER_RECORD = """\ +{{ + "type": "This is a previous conversation with the user", + "question": "{question}", + "answer": "{answer}", +}} +""" + + +class SaveLoad(): + + def does_checkpoint_exist(self, checkpoint_dir=None): + import os, glob + if checkpoint_dir is None: checkpoint_dir = self.checkpoint_dir + if not os.path.exists(checkpoint_dir): return False + if len(glob.glob(os.path.join(checkpoint_dir, "*.json"))) == 0: return False + return True + + def save_to_checkpoint(self, checkpoint_dir=None): + print(f'saving vector store to: {checkpoint_dir}') + if checkpoint_dir is None: checkpoint_dir = self.checkpoint_dir + self.vs_index.storage_context.persist(persist_dir=checkpoint_dir) + + def load_from_checkpoint(self, checkpoint_dir=None): + if checkpoint_dir is None: checkpoint_dir = self.checkpoint_dir + if self.does_checkpoint_exist(checkpoint_dir=checkpoint_dir): + print('loading checkpoint from disk') + from llama_index.core import StorageContext, load_index_from_storage + storage_context = StorageContext.from_defaults(persist_dir=checkpoint_dir) + self.vs_index = load_index_from_storage(storage_context, embed_model=self.embed_model) + return self.vs_index + else: + return self.create_new_vs() + + def create_new_vs(self): + return GptacVectorStoreIndex.default_vector_store(embed_model=self.embed_model) + + def purge(self): + import shutil + shutil.rmtree(self.checkpoint_dir, ignore_errors=True) + self.vs_index = self.create_new_vs() + + +class LlamaIndexRagWorker(SaveLoad): + def __init__(self, user_name, llm_kwargs, auto_load_checkpoint=True, checkpoint_dir=None) -> None: + self.debug_mode = True + self.embed_model = OpenAiEmbeddingModel(llm_kwargs) + self.user_name = user_name + self.checkpoint_dir = checkpoint_dir + if auto_load_checkpoint: + self.vs_index = self.load_from_checkpoint(checkpoint_dir) + else: + self.vs_index = self.create_new_vs(checkpoint_dir) + atexit.register(lambda: self.save_to_checkpoint(checkpoint_dir)) + + def assign_embedding_model(self): + pass + + def inspect_vector_store(self): + # This function is for debugging + self.vs_index.storage_context.index_store.to_dict() + docstore = self.vs_index.storage_context.docstore.docs + vector_store_preview = "\n".join([ f"{_id} | {tn.text}" for _id, tn in docstore.items() ]) + print('\n++ --------inspect_vector_store begin--------') + print(vector_store_preview) + print('oo --------inspect_vector_store end--------') + return vector_store_preview + + def add_documents_to_vector_store(self, document_list): + documents = [Document(text=t) for t in document_list] + documents_nodes = run_transformations( + documents, # type: ignore + self.vs_index._transformations, + show_progress=True + ) + self.vs_index.insert_nodes(documents_nodes) + if self.debug_mode: self.inspect_vector_store() + + def add_text_to_vector_store(self, text): + node = TextNode(text=text) + documents_nodes = run_transformations( + [node], + self.vs_index._transformations, + show_progress=True + ) + self.vs_index.insert_nodes(documents_nodes) + if self.debug_mode: self.inspect_vector_store() + + def remember_qa(self, question, answer): + formatted_str = QUESTION_ANSWER_RECORD.format(question=question, answer=answer) + self.add_text_to_vector_store(formatted_str) + + def retrieve_from_store_with_query(self, query): + if self.debug_mode: self.inspect_vector_store() + retriever = self.vs_index.as_retriever() + return retriever.retrieve(query) + + def build_prompt(self, query, nodes): + context_str = self.generate_node_array_preview(nodes) + return DEFAULT_QUERY_GENERATION_PROMPT.format(context_str=context_str, query_str=query) + + def generate_node_array_preview(self, nodes): + buf = "\n".join(([f"(No.{i+1} | score {n.score:.3f}): {n.text}" for i, n in enumerate(nodes)])) + if self.debug_mode: print(buf) + return buf diff --git a/crazy_functions/rag_fns/milvus_worker.py b/crazy_functions/rag_fns/milvus_worker.py new file mode 100644 index 000000000..4b5b0ad99 --- /dev/null +++ b/crazy_functions/rag_fns/milvus_worker.py @@ -0,0 +1,107 @@ +import llama_index +import os +import atexit +from typing import List +from llama_index.core import Document +from llama_index.core.schema import TextNode +from request_llms.embed_models.openai_embed import OpenAiEmbeddingModel +from shared_utils.connect_void_terminal import get_chat_default_kwargs +from llama_index.core import VectorStoreIndex, SimpleDirectoryReader +from crazy_functions.rag_fns.vector_store_index import GptacVectorStoreIndex +from llama_index.core.ingestion import run_transformations +from llama_index.core import PromptTemplate +from llama_index.core.response_synthesizers import TreeSummarize +from llama_index.core import StorageContext +from llama_index.vector_stores.milvus import MilvusVectorStore +from crazy_functions.rag_fns.llama_index_worker import LlamaIndexRagWorker + +DEFAULT_QUERY_GENERATION_PROMPT = """\ +Now, you have context information as below: +--------------------- +{context_str} +--------------------- +Answer the user request below (use the context information if necessary, otherwise you can ignore them): +--------------------- +{query_str} +""" + +QUESTION_ANSWER_RECORD = """\ +{{ + "type": "This is a previous conversation with the user", + "question": "{question}", + "answer": "{answer}", +}} +""" + + +class MilvusSaveLoad(): + + def does_checkpoint_exist(self, checkpoint_dir=None): + import os, glob + if checkpoint_dir is None: checkpoint_dir = self.checkpoint_dir + if not os.path.exists(checkpoint_dir): return False + if len(glob.glob(os.path.join(checkpoint_dir, "*.json"))) == 0: return False + return True + + def save_to_checkpoint(self, checkpoint_dir=None): + print(f'saving vector store to: {checkpoint_dir}') + # if checkpoint_dir is None: checkpoint_dir = self.checkpoint_dir + # self.vs_index.storage_context.persist(persist_dir=checkpoint_dir) + + def load_from_checkpoint(self, checkpoint_dir=None): + if checkpoint_dir is None: checkpoint_dir = self.checkpoint_dir + if self.does_checkpoint_exist(checkpoint_dir=checkpoint_dir): + print('loading checkpoint from disk') + from llama_index.core import StorageContext, load_index_from_storage + storage_context = StorageContext.from_defaults(persist_dir=checkpoint_dir) + try: + self.vs_index = load_index_from_storage(storage_context, embed_model=self.embed_model) + return self.vs_index + except: + return self.create_new_vs(checkpoint_dir) + else: + return self.create_new_vs(checkpoint_dir) + + def create_new_vs(self, checkpoint_dir, overwrite=False): + vector_store = MilvusVectorStore( + uri=os.path.join(checkpoint_dir, "milvus_demo.db"), + dim=self.embed_model.embedding_dimension(), + overwrite=overwrite + ) + storage_context = StorageContext.from_defaults(vector_store=vector_store) + index = GptacVectorStoreIndex.default_vector_store(storage_context=storage_context, embed_model=self.embed_model) + return index + + def purge(self): + self.vs_index = self.create_new_vs(self.checkpoint_dir, overwrite=True) + +class MilvusRagWorker(MilvusSaveLoad, LlamaIndexRagWorker): + + def __init__(self, user_name, llm_kwargs, auto_load_checkpoint=True, checkpoint_dir=None) -> None: + self.debug_mode = True + self.embed_model = OpenAiEmbeddingModel(llm_kwargs) + self.user_name = user_name + self.checkpoint_dir = checkpoint_dir + if auto_load_checkpoint: + self.vs_index = self.load_from_checkpoint(checkpoint_dir) + else: + self.vs_index = self.create_new_vs(checkpoint_dir) + atexit.register(lambda: self.save_to_checkpoint(checkpoint_dir)) + + def inspect_vector_store(self): + # This function is for debugging + try: + self.vs_index.storage_context.index_store.to_dict() + docstore = self.vs_index.storage_context.docstore.docs + if not docstore.items(): + raise ValueError("cannot inspect") + vector_store_preview = "\n".join([ f"{_id} | {tn.text}" for _id, tn in docstore.items() ]) + except: + dummy_retrieve_res: List["NodeWithScore"] = self.vs_index.as_retriever().retrieve(' ') + vector_store_preview = "\n".join( + [f"{node.id_} | {node.text}" for node in dummy_retrieve_res] + ) + print('\n++ --------inspect_vector_store begin--------') + print(vector_store_preview) + print('oo --------inspect_vector_store end--------') + return vector_store_preview diff --git a/crazy_functions/rag_fns/vector_store_index.py b/crazy_functions/rag_fns/vector_store_index.py new file mode 100644 index 000000000..74e8b09df --- /dev/null +++ b/crazy_functions/rag_fns/vector_store_index.py @@ -0,0 +1,58 @@ +from llama_index.core import VectorStoreIndex +from typing import Any, List, Optional + +from llama_index.core.callbacks.base import CallbackManager +from llama_index.core.schema import TransformComponent +from llama_index.core.service_context import ServiceContext +from llama_index.core.settings import ( + Settings, + callback_manager_from_settings_or_context, + transformations_from_settings_or_context, +) +from llama_index.core.storage.storage_context import StorageContext + + +class GptacVectorStoreIndex(VectorStoreIndex): + + @classmethod + def default_vector_store( + cls, + storage_context: Optional[StorageContext] = None, + show_progress: bool = False, + callback_manager: Optional[CallbackManager] = None, + transformations: Optional[List[TransformComponent]] = None, + # deprecated + service_context: Optional[ServiceContext] = None, + embed_model = None, + **kwargs: Any, + ): + """Create index from documents. + + Args: + documents (Optional[Sequence[BaseDocument]]): List of documents to + build the index from. + + """ + storage_context = storage_context or StorageContext.from_defaults() + docstore = storage_context.docstore + callback_manager = ( + callback_manager + or callback_manager_from_settings_or_context(Settings, service_context) + ) + transformations = transformations or transformations_from_settings_or_context( + Settings, service_context + ) + + with callback_manager.as_trace("index_construction"): + + return cls( + nodes=[], + storage_context=storage_context, + callback_manager=callback_manager, + show_progress=show_progress, + transformations=transformations, + service_context=service_context, + embed_model=embed_model, + **kwargs, + ) + diff --git a/docs/translate_english.json b/docs/translate_english.json index f82bfecb9..db5177141 100644 --- a/docs/translate_english.json +++ b/docs/translate_english.json @@ -291,7 +291,7 @@ "gradio的inbrowser触发不太稳定": "In-browser triggering of gradio is not very stable", "回滚代码到原始的浏览器打开函数": "Roll back code to the original browser open function", "打开浏览器": "Open browser", - "ChatGPT 学术优化": "ChatGPT academic optimization", + "ChatGPT 学术优化": "GPT Academic", "代码开源和更新": "Code open source and updates", "地址🚀": "Address 🚀", "感谢热情的": "Thanks to the enthusiastic", @@ -3976,5 +3976,234 @@ "为了更灵活地接入vllm多模型管理界面": "To more flexibly access the vllm multi-model management interface", "读取解析": "Read and parse", "允许缓存": "Allow caching", - "Run2 中对 Kaon 鉴别的要求被收紧为 $ \\operatorname{ProbNNk}\\left": "The requirement for Kaon discrimination in Run2 has been tightened to $ \\operatorname{ProbNNk}\\left" + "Run2 中对 Kaon 鉴别的要求被收紧为 $ \\operatorname{ProbNNk}\\left": "The requirement for Kaon discrimination in Run2 has been tightened to $ \\operatorname{ProbNNk}\\left", + "当前使用人数太多": "Current user count is too high", + "提取historyBox信息": "Extract historyBox information", + "📚Arxiv论文精细翻译": "Fine translation of 📚Arxiv papers", + "检索中": "Searching", + "受到限制": "Restricted", + "3. 历史输入包含图像": "3. Historical input contains images", + "待通过互联网检索的问题": "Questions to be retrieved via the internet", + "使用 findall 方法查找所有匹配的 Base64 字符串": "Use findall method to find all matching Base64 strings", + "建立文件树": "Build file tree", + "经过clip": "Through clip", + "增强": "Enhance", + "对话存档": "Conversation archive", + "网页": "Webpage", + "怎么下载相关论文": "How to download related papers", + "当前对话是关于 Nginx 的介绍和使用等": "The current conversation is about the introduction and use of Nginx, etc.", + "从而提高学术论文检索的精度": "To improve the accuracy of academic paper retrieval", + "使用自然语言实现您的想法": "Implement your ideas using natural language", + "这样可以保证后续问答能读取到有效的历史记录": "This ensures that subsequent questions and answers can read valid historical records", + "生成对比html": "Generate comparison html", + "Doc2x API 页数受限": "Doc2x API page count limited", + "inputs 本次请求": "Inputs for this request", + "有其原问题": "Has its original question", + "在线搜索失败": "Online search failed", + "选择搜索引擎": "Choose a search engine", + "同步已知模型的其他信息": "Synchronize other information of known models", + "在线搜索服务": "Online search service", + "常规对话": "Regular conversation", + "使用正则表达式匹配模式": "Use regular expressions to match patterns", + "从而提高网页检索的精度": "To improve the accuracy of webpage retrieval", + "GPT-Academic输出文档": "GPT-Academic output document", + "/* 小按钮 */": "/* Small button */", + "历史记录": "History record", + "上传一系列python源文件": "Upload a series of python source files", + "仅DALLE3生效": "Only DALLE3 takes effect", + "判断给定的单个字符是否是全角字符": "Determine if the given single character is a full-width character", + "依次放入每组第一": "Put each group's first one by one", + "这部分代码会逐渐移动到common.js中": "This part of the code will gradually move to common.js", + "列出机器学习的三种应用": "List three applications of machine learning", + "更新主输入区的参数": "Update the parameters of the main input area", + "从以上搜索结果中抽取与问题": "Extract from the above search results related to the question", + "* 如果解码失败": "* If decoding fails", + "如果是已知模型": "If it is a known model", + "一": "One", + "模型切换时的回调": "Callback when switching models", + "加入历史": "Add to history", + "压缩结果": "Compress the result", + "使用 DALLE2/DALLE3 生成图片 | 输入参数字符串": "Use DALLE2/DALLE3 to generate images | Input parameter string", + "搜索分类": "Search category", + "获得空的回复": "Get an empty reply", + "多模态模型": "Multimodal model", + "移除注释": "Remove comments", + "对话背景": "Conversation background", + "获取需要执行的插件名称": "Get the name of the plugin to be executed", + "是否启动语音输入功能": "Whether to enable voice input function", + "更新高级参数输入区的参数": "Update the parameters of the advanced parameter input area", + "启用多模态能力": "Enable multimodal capabilities", + "请根据以上搜索结果回答问题": "Please answer the question based on the above search results", + "生成的问题要求指向对象清晰明确": "The generated question requires clear and specific references to the object", + "Arxiv论文翻译": "Translation of Arxiv paper", + "找不到该模型": "Model not found", + "提取匹配的数字部分并转换为整数": "Extract matching numeric parts and convert to integers", + "尝试进行搜索优化": "Try to optimize the search", + "重新梳理输入参数": "Reorganize input parameters", + "存储翻译好的arxiv论文的路径": "Path to store translated arxiv papers", + "尽量使用英文": "Use English as much as possible", + "插件二级菜单的实现": "Implementation of plugin submenus", + "* 增强优化": "Enhanced optimization", + "但属于其他用户": "But belongs to another user", + "不得有多余字符": "No extra characters allowed", + "怎么解决": "How to solve", + "根据综合回答问题": "Answer questions comprehensively", + "降低温度再试一次": "Lower the temperature and try again", + "作为一个网页搜索助手": "As a web search assistant", + "支持将文件直接粘贴到输入区": "Support pasting files directly into the input area", + "打开新对话": "Open a new conversation", + "但位置非法": "But the position is illegal", + "会自动读取输入框内容": "Will automatically read the input box content", + "移除模块的文档字符串": "Remove the module's docstrings", + "from crazy_functions.联网的ChatGPT_bing版 import 连接bing搜索回答问题": "from crazy_functions.online.ChatGPT_bing import connect_bing_search_to_answer_questions", + "关闭": "Close", + "学术论文": "Academic paper", + "多模态能力": "Multimodal capabilities", + "无渲染": "No rendering", + "弃用功能": "Deprecated feature", + "输入Searxng的地址": "Enter the address of Searxng", + "风格": "Style", + "介绍下第2点": "Introduce the second point", + "你的任务是结合历史记录": "Your task is to combine historical records", + "前端": "Frontend", + "采取措施丢弃一部分文本": "Take measures to discard some text", + "2. 输入包含图像": "2. Input contains images", + "输入问题": "Input question", + "可能原因": "Possible reasons", + "2. Java 是一种面向对象的编程语言": "Java is an object-oriented programming language", + "不支持的检索类型": "Unsupported retrieval type", + "第四步": "Step four", + "2. 机器学习在自然语言处理中的应用": "Applications of machine learning in natural language processing", + "浮动菜单定义": "Definition of floating menu", + "鿿": "Undefined", + "history 历史上下文": "History context", + "1. Java 是一种编译型语言": "Java is a compiled language", + "请根据给定的若干条搜索结果回答问题": "Answer the question based on the given search results", + "当输入文本 + 历史文本超出最大限制时": "When the input text + historical text exceeds the maximum limit", + "限DALLE3": "Limited to DALLE3", + "原问题": "Original question", + "日志文件": "Log file", + "输入图片描述": "Input image description", + "示例使用": "Example usage", + "后续参数": "Subsequent parameters", + "请用一句话对下面的程序文件做一个整体概述": "Please give a brief overview of the program file below in one sentence", + "当前对话是关于深度学习的介绍和应用等": "The current conversation is about the introduction and applications of deep learning", + "点击这里输入「关键词」搜索插件": "Click here to enter 'keywords' search plugin", + "按用户划分": "Divided by user", + "将结果写回源文件": "Write the results back to the source file", + "使用前切换到GPT系列模型": "Switch to GPT series model before using", + "正在读取下一段代码片段": "Reading the next code snippet", + "第二个搜索结果": "Second search result", + "作为一个学术论文搜索助手": "As an academic paper search assistant", + "搜索": "Search", + "无法从searxng获取信息!请尝试更换搜索引擎": "Unable to retrieve information from searxng! Please try changing the search engine", + "* 清洗搜索结果": "Cleaning search results", + "或者压缩包": "Or compressed file", + "模型": "Model", + "切换布局": "Switch layout", + "生成当前浏览器窗口的uuid": "Generate the uuid of the current browser window", + "左上角工具栏定义": "Definition of the top-left toolbar", + "from crazy_functions.联网的ChatGPT import ConnectToNetworkToAnswerQuestions": "from crazy_functions.ConnectToNetworkToAnswerQuestions import ChatGPT", + "对最相关的三个搜索结果进行总结": "Summarize the top three most relevant search results", + "刷新失效": "Refresh invalid", + "将处理后的 AST 转换回源代码": "Convert the processed AST back to source code", + "/* 插件下拉菜单 */": "/* Plugin dropdown menu */", + "移除类的文档字符串": "Remove the documentation strings of a class", + "请尽量不要修改": "Please try not to modify", + "并更换新的 API 秘钥": "And replace with a new API key", + "输入文件的路径": "Input file path", + "发现异常嵌套公式": "Identify nested formula exceptions", + "修复不标准的dollar公式符号的问题": "Fix the issue of non-standard dollar formula symbols", + "Searxng互联网检索服务": "Searxng Internet search service", + "联网检索中": "In network retrieval", + "并与“原问题语言相同”": "And in the same language as the original question", + "存在": "Exists", + "列出Java的三种特点": "List three characteristics of Java", + "3. Java 是一种跨平台的编程语言": "3. Java is a cross-platform programming language", + "所有源文件均已处理完毕": "All source files have been processed", + "限DALLE2": "Limited to DALLE2", + "紫东太初大模型 https": "Zidong Taichu Large Model https", + "🎨图片生成": "🎨 Image generation", + "1. 模型本身是多模态模型": "1. The model itself is multimodal", + "相关的信息": "Related information", + "* 或者使用搜索优化器": "* Or use a search optimizer", + "搜索查询": "Search query", + "当前对话是关于 Nginx 的介绍和在Ubuntu上的使用等": "The current conversation is about the introduction of Nginx and its use on Ubuntu, etc.", + "必须以json形式给出": "Must be provided in JSON format", + "开启": "Turn on", + "1. 机器学习在图像识别中的应用": "1. The application of machine learning in image recognition", + "处理代码片段": "Processing code snippet", + "则尝试获取其信息": "Then try to get its information", + "已完成的文件": "Completed file", + "注意这可能会消耗较多token": "Note that this may consume more tokens", + "多模型对话": "Multi-model conversation", + "现在有历史记录": "Now there is a history record", + "你知道 Python 么": "Do you know Python?", + "Base64编码": "Base64 encoding", + "Gradio的inbrowser触发不太稳定": "Gradio's in-browser trigger is not very stable", + "CJK标点符号": "CJK punctuation marks", + "请联系 Doc2x 方面": "Please contact Doc2x for details", + "耗尽generator避免报错": "Exhaust the generator to avoid errors", + "📚本地Latex论文精细翻译": "📚 Local Latex paper finely translated", + "* 尝试解码优化后的搜索结果": "* Try to decode the optimized search results", + "为这些代码添加docstring | 输入参数为路径": "Add docstring for these codes | Input parameter is path", + "读取插件参数": "Read plugin parameters", + "如果剩余的行数非常少": "If the remaining lines are very few", + "输出格式为JSON": "Output format is JSON", + "提取QaBox信息": "Extract QaBox information", + "不使用多模态能力": "Not using multimodal capabilities", + "解析源代码为 AST": "Parse source code into AST", + "使用前请切换模型到GPT系列": "Switch the model to GPT series before using", + "中文字符": "Chinese characters", + "用户的上传目录": "User's upload directory", + "请将文件上传后再执行该任务": "Please upload the file before executing this task", + "移除函数的文档字符串": "Remove the function's docstring", + "新版-更流畅": "New version - smoother", + "检索词": "Search term", + "获取插件参数": "Get plugin parameters", + "获取插件执行函数": "Get plugin execution function", + "为“原问题”生成个不同版本的“检索词”": "Generate different versions of 'search terms' for the 'original question'", + "并清洗重复的搜索结果": "Clean and remove duplicate search results", + "直接返回原始问题": "Directly return the original question", + "从不同角度": "From different perspectives", + "展示已经完成的部分": "Display completed parts", + "搜索优化": "Search optimization", + "解决缩进问题": "Resolve indentation issues", + "直接给出最多{num}个检索词": "Directly provide up to {num} search terms", + "对话数据": "Conversation data", + "定义一个正则表达式来匹配 Base64 字符串": "Define a regular expression to match Base64 strings", + "转化为kwargs字典": "Convert to kwargs dictionary", + "原始数据": "Original data", + "当以下条件满足时": "When the following conditions are met", + "主题修改": "Topic modification", + "Searxng服务地址": "Searxng service address", + "3. 机器学习在推荐系统中的应用": "3. Application of machine learning in recommendation systems", + "全角符号": "Full-width symbols", + "发送到大模型进行分析": "Send for analysis to a large model", + "一个常用的测试目录": "A commonly used test directory", + "在线搜索失败!": "Online search failed!", + "搜索语言": "Search language", + "万事俱备": "All is ready", + "指定了后续参数的名称": "Specified the names of subsequent parameters", + "是否使用搜索增强": "Whether to use search enhancement", + "你知道 GAN 么": "Do you know about GAN?", + "├── 互联网检索": "├── Internet retrieval", + "公式之中出现了异常": "An anomaly occurred in the formula", + "当前对话是关于深度学习的介绍和在图像识别中的应用等": "The current conversation is about the introduction of deep learning and its applications in image recognition, etc.", + "返回反转后的 Base64 字符串列表": "Return a list of Base64 strings reversed", + "一鼓作气处理掉": "Deal with it in one go", + "剩余源文件数量": "Remaining source file count", + "查互联网后回答": "Answer after checking the internet", + "需要生成图像的文本描述": "Text description for generating images", + "* 如果再次失败": "If failed again", + "质量": "Quality", + "请配置 TAICHU_API_KEY": "Please configure TAICHU_API_KEY", + "most_recent_uploaded 是一个放置最新上传图像的路径": "most_recent_uploaded is a path to place the latest uploaded images", + "真正的参数": "Actual parameters", + "生成带注释文件": "Generate files with annotations", + "源自": "From", + "怎么下载": "How to download", + "请稍后": "Please wait", + "会尝试结合历史记录进行搜索优化": "Will try to optimize the search by combining historical records", + "max_token_limit 最大token限制": "max_token_limit maximum token limit" } \ No newline at end of file diff --git a/docs/translate_std.json b/docs/translate_std.json index 0b3faf428..50889da79 100644 --- a/docs/translate_std.json +++ b/docs/translate_std.json @@ -106,5 +106,7 @@ "解析PDF_DOC2X_转Latex": "ParsePDF_DOC2X_toLatex", "解析PDF_基于DOC2X": "ParsePDF_basedDOC2X", "解析PDF_简单拆解": "ParsePDF_simpleDecomposition", - "解析PDF_DOC2X_单文件": "ParsePDF_DOC2X_singleFile" + "解析PDF_DOC2X_单文件": "ParsePDF_DOC2X_singleFile", + "注释Python项目": "CommentPythonProject", + "注释源代码": "CommentSourceCode" } \ No newline at end of file diff --git a/main.py b/main.py index 8de1cc2cc..294f64aff 100644 --- a/main.py +++ b/main.py @@ -118,8 +118,8 @@ def main(): choices=[ "常规对话", "多模型对话", + "智能召回 RAG", # "智能上下文", - # "智能召回 RAG", ], value="常规对话", interactive=True, label='', show_label=False, elem_classes='normal_mut_select', elem_id="gpt-submit-dropdown").style(container=False) diff --git a/request_llms/bridge_all.py b/request_llms/bridge_all.py index 931a899b5..d52c4d7fc 100644 --- a/request_llms/bridge_all.py +++ b/request_llms/bridge_all.py @@ -349,6 +349,23 @@ def decode(self, *args, **kwargs): "token_cnt": get_token_num_gpt4, }, + "o1-preview": { + "fn_with_ui": chatgpt_ui, + "fn_without_ui": chatgpt_noui, + "endpoint": openai_endpoint, + "max_token": 128000, + "tokenizer": tokenizer_gpt4, + "token_cnt": get_token_num_gpt4, + }, + "o1-mini": { + "fn_with_ui": chatgpt_ui, + "fn_without_ui": chatgpt_noui, + "endpoint": openai_endpoint, + "max_token": 128000, + "tokenizer": tokenizer_gpt4, + "token_cnt": get_token_num_gpt4, + }, + "gpt-4-turbo": { "fn_with_ui": chatgpt_ui, "fn_without_ui": chatgpt_noui, @@ -508,22 +525,46 @@ def decode(self, *args, **kwargs): "tokenizer": tokenizer_gpt35, "token_cnt": get_token_num_gpt35, }, + # Gemini + # Note: now gemini-pro is an alias of gemini-1.0-pro. + # Warning: gemini-pro-vision has been deprecated. + # Support for gemini-pro-vision has been removed. "gemini-pro": { "fn_with_ui": genai_ui, "fn_without_ui": genai_noui, "endpoint": gemini_endpoint, + "has_multimodal_capacity": False, "max_token": 1024 * 32, "tokenizer": tokenizer_gpt35, "token_cnt": get_token_num_gpt35, }, - "gemini-pro-vision": { + "gemini-1.0-pro": { "fn_with_ui": genai_ui, "fn_without_ui": genai_noui, "endpoint": gemini_endpoint, + "has_multimodal_capacity": False, "max_token": 1024 * 32, "tokenizer": tokenizer_gpt35, "token_cnt": get_token_num_gpt35, }, + "gemini-1.5-pro": { + "fn_with_ui": genai_ui, + "fn_without_ui": genai_noui, + "endpoint": gemini_endpoint, + "has_multimodal_capacity": True, + "max_token": 1024 * 204800, + "tokenizer": tokenizer_gpt35, + "token_cnt": get_token_num_gpt35, + }, + "gemini-1.5-flash": { + "fn_with_ui": genai_ui, + "fn_without_ui": genai_noui, + "endpoint": gemini_endpoint, + "has_multimodal_capacity": True, + "max_token": 1024 * 204800, + "tokenizer": tokenizer_gpt35, + "token_cnt": get_token_num_gpt35, + }, # cohere "cohere-command-r-plus": { @@ -958,7 +999,7 @@ def decode(self, *args, **kwargs): }) except: print(trimmed_format_exc()) -if "sparkv3" in AVAIL_LLM_MODELS or "sparkv3.5" in AVAIL_LLM_MODELS: # 讯飞星火认知大模型 +if any(x in AVAIL_LLM_MODELS for x in ("sparkv3", "sparkv3.5", "sparkv4")): # 讯飞星火认知大模型 try: from .bridge_spark import predict_no_ui_long_connection as spark_noui from .bridge_spark import predict as spark_ui diff --git a/request_llms/bridge_chatglm3.py b/request_llms/bridge_chatglm3.py index d79067b67..95b629d19 100644 --- a/request_llms/bridge_chatglm3.py +++ b/request_llms/bridge_chatglm3.py @@ -18,7 +18,7 @@ def load_model_info(self): def load_model_and_tokenizer(self): # 🏃‍♂️🏃‍♂️🏃‍♂️ 子进程执行 - from transformers import AutoModel, AutoTokenizer + from transformers import AutoModel, AutoTokenizer, BitsAndBytesConfig import os, glob import os import platform @@ -45,15 +45,13 @@ def load_model_and_tokenizer(self): chatglm_model = AutoModel.from_pretrained( pretrained_model_name_or_path=_model_name_, trust_remote_code=True, - device="cuda", - load_in_4bit=True, + quantization_config=BitsAndBytesConfig(load_in_4bit=True), ) elif LOCAL_MODEL_QUANT == "INT8": # INT8 chatglm_model = AutoModel.from_pretrained( pretrained_model_name_or_path=_model_name_, trust_remote_code=True, - device="cuda", - load_in_8bit=True, + quantization_config=BitsAndBytesConfig(load_in_8bit=True), ) else: chatglm_model = AutoModel.from_pretrained( diff --git a/request_llms/bridge_google_gemini.py b/request_llms/bridge_google_gemini.py index 9bf4e23a6..3697b8fcc 100644 --- a/request_llms/bridge_google_gemini.py +++ b/request_llms/bridge_google_gemini.py @@ -8,15 +8,15 @@ import time from request_llms.com_google import GoogleChatInit from toolbox import ChatBotWithCookies -from toolbox import get_conf, update_ui, update_ui_lastest_msg, have_any_recent_upload_image_files, trimmed_format_exc, log_chat +from toolbox import get_conf, update_ui, update_ui_lastest_msg, have_any_recent_upload_image_files, trimmed_format_exc, log_chat, encode_image proxies, TIMEOUT_SECONDS, MAX_RETRY = get_conf('proxies', 'TIMEOUT_SECONDS', 'MAX_RETRY') timeout_bot_msg = '[Local Message] Request timeout. Network error. Please check proxy settings in config.py.' + \ '网络错误,检查代理服务器是否可用,以及代理设置的格式是否正确,格式须是[协议]://[地址]:[端口],缺一不可。' -def predict_no_ui_long_connection(inputs, llm_kwargs, history=[], sys_prompt="", observe_window=None, - console_slience=False): +def predict_no_ui_long_connection(inputs:str, llm_kwargs:dict, history:list=[], sys_prompt:str="", observe_window:list=[], + console_slience:bool=False): # 检查API_KEY if get_conf("GEMINI_API_KEY") == "": raise ValueError(f"请配置 GEMINI_API_KEY。") @@ -44,9 +44,20 @@ def predict_no_ui_long_connection(inputs, llm_kwargs, history=[], sys_prompt="", raise RuntimeError(f'{gpt_replying_buffer} 对话错误') return gpt_replying_buffer +def make_media_input(inputs, image_paths): + image_base64_array = [] + for image_path in image_paths: + path = os.path.abspath(image_path) + inputs = inputs + f'

' + base64 = encode_image(path) + image_base64_array.append(base64) + return inputs, image_base64_array def predict(inputs:str, llm_kwargs:dict, plugin_kwargs:dict, chatbot:ChatBotWithCookies, history:list=[], system_prompt:str='', stream:bool=True, additional_fn:str=None): + + from .bridge_all import model_info + # 检查API_KEY if get_conf("GEMINI_API_KEY") == "": yield from update_ui_lastest_msg(f"请配置 GEMINI_API_KEY。", chatbot=chatbot, history=history, delay=0) @@ -57,18 +68,17 @@ def predict(inputs:str, llm_kwargs:dict, plugin_kwargs:dict, chatbot:ChatBotWith from core_functional import handle_core_functionality inputs, history = handle_core_functionality(additional_fn, inputs, history, chatbot) - if "vision" in llm_kwargs["llm_model"]: - have_recent_file, image_paths = have_any_recent_upload_image_files(chatbot) - if not have_recent_file: - chatbot.append((inputs, "没有检测到任何近期上传的图像文件,请上传jpg格式的图片,此外,请注意拓展名需要小写")) - yield from update_ui(chatbot=chatbot, history=history, msg="等待图片") # 刷新界面 - return - def make_media_input(inputs, image_paths): - for image_path in image_paths: - inputs = inputs + f'

' - return inputs - if have_recent_file: - inputs = make_media_input(inputs, image_paths) + # multimodal capacity + # inspired by codes in bridge_chatgpt + has_multimodal_capacity = model_info[llm_kwargs['llm_model']].get('has_multimodal_capacity', False) + if has_multimodal_capacity: + has_recent_image_upload, image_paths = have_any_recent_upload_image_files(chatbot, pop=True) + else: + has_recent_image_upload, image_paths = False, [] + if has_recent_image_upload: + inputs, image_base64_array = make_media_input(inputs, image_paths) + else: + inputs, image_base64_array = inputs, [] chatbot.append((inputs, "")) yield from update_ui(chatbot=chatbot, history=history) @@ -76,7 +86,7 @@ def make_media_input(inputs, image_paths): retry = 0 while True: try: - stream_response = genai.generate_chat(inputs, llm_kwargs, history, system_prompt) + stream_response = genai.generate_chat(inputs, llm_kwargs, history, system_prompt, image_base64_array, has_multimodal_capacity) break except Exception as e: retry += 1 @@ -112,7 +122,6 @@ def make_media_input(inputs, image_paths): yield from update_ui(chatbot=chatbot, history=history) - if __name__ == '__main__': import sys llm_kwargs = {'llm_model': 'gemini-pro'} diff --git a/request_llms/com_google.py b/request_llms/com_google.py index 75f6b53b4..9df3a9977 100644 --- a/request_llms/com_google.py +++ b/request_llms/com_google.py @@ -7,7 +7,7 @@ import re import requests from typing import List, Dict, Tuple -from toolbox import get_conf, encode_image, get_pictures_list, to_markdown_tabs +from toolbox import get_conf, update_ui, encode_image, get_pictures_list, to_markdown_tabs proxies, TIMEOUT_SECONDS = get_conf("proxies", "TIMEOUT_SECONDS") @@ -112,6 +112,14 @@ def html_local_img(__file, layout="left", max_width=None, max_height=None, md=Tr return a +def reverse_base64_from_input(inputs): + pattern = re.compile(r'

]+base64="([^"]+)">
') + base64_strings = pattern.findall(inputs) + return base64_strings + +def contain_base64(inputs): + base64_strings = reverse_base64_from_input(inputs) + return len(base64_strings) > 0 class GoogleChatInit: def __init__(self, llm_kwargs): @@ -119,9 +127,9 @@ def __init__(self, llm_kwargs): endpoint = model_info[llm_kwargs['llm_model']]['endpoint'] self.url_gemini = endpoint + "/%m:streamGenerateContent?key=%k" - def generate_chat(self, inputs, llm_kwargs, history, system_prompt): + def generate_chat(self, inputs, llm_kwargs, history, system_prompt, image_base64_array:list=[], has_multimodal_capacity:bool=False): headers, payload = self.generate_message_payload( - inputs, llm_kwargs, history, system_prompt + inputs, llm_kwargs, history, system_prompt, image_base64_array, has_multimodal_capacity ) response = requests.post( url=self.url_gemini, @@ -133,13 +141,16 @@ def generate_chat(self, inputs, llm_kwargs, history, system_prompt): ) return response.iter_lines() - def __conversation_user(self, user_input, llm_kwargs): + def __conversation_user(self, user_input, llm_kwargs, enable_multimodal_capacity): what_i_have_asked = {"role": "user", "parts": []} - if "vision" not in self.url_gemini: + from .bridge_all import model_info + + if enable_multimodal_capacity: + input_, encode_img = input_encode_handler(user_input, llm_kwargs=llm_kwargs) + else: input_ = user_input encode_img = [] - else: - input_, encode_img = input_encode_handler(user_input, llm_kwargs=llm_kwargs) + what_i_have_asked["parts"].append({"text": input_}) if encode_img: for data in encode_img: @@ -153,12 +164,12 @@ def __conversation_user(self, user_input, llm_kwargs): ) return what_i_have_asked - def __conversation_history(self, history, llm_kwargs): + def __conversation_history(self, history, llm_kwargs, enable_multimodal_capacity): messages = [] conversation_cnt = len(history) // 2 if conversation_cnt: for index in range(0, 2 * conversation_cnt, 2): - what_i_have_asked = self.__conversation_user(history[index], llm_kwargs) + what_i_have_asked = self.__conversation_user(history[index], llm_kwargs, enable_multimodal_capacity) what_gpt_answer = { "role": "model", "parts": [{"text": history[index + 1]}], @@ -168,7 +179,7 @@ def __conversation_history(self, history, llm_kwargs): return messages def generate_message_payload( - self, inputs, llm_kwargs, history, system_prompt + self, inputs, llm_kwargs, history, system_prompt, image_base64_array:list=[], has_multimodal_capacity:bool=False ) -> Tuple[Dict, Dict]: messages = [ # {"role": "system", "parts": [{"text": system_prompt}]}, # gemini 不允许对话轮次为偶数,所以这个没有用,看后续支持吧。。。 @@ -179,21 +190,29 @@ def generate_message_payload( "%m", llm_kwargs["llm_model"] ).replace("%k", get_conf("GEMINI_API_KEY")) header = {"Content-Type": "application/json"} - if "vision" not in self.url_gemini: # 不是vision 才处理history + + if has_multimodal_capacity: + enable_multimodal_capacity = (len(image_base64_array) > 0) or any([contain_base64(h) for h in history]) + else: + enable_multimodal_capacity = False + + if not enable_multimodal_capacity: messages.extend( - self.__conversation_history(history, llm_kwargs) + self.__conversation_history(history, llm_kwargs, enable_multimodal_capacity) ) # 处理 history - messages.append(self.__conversation_user(inputs, llm_kwargs)) # 处理用户对话 + + messages.append(self.__conversation_user(inputs, llm_kwargs, enable_multimodal_capacity)) # 处理用户对话 payload = { "contents": messages, "generationConfig": { - # "maxOutputTokens": 800, + # "maxOutputTokens": llm_kwargs.get("max_token", 1024), "stopSequences": str(llm_kwargs.get("stop", "")).split(" "), "temperature": llm_kwargs.get("temperature", 1), "topP": llm_kwargs.get("top_p", 0.8), "topK": 10, }, } + return header, payload diff --git a/request_llms/embed_models/bridge_all_embed.py b/request_llms/embed_models/bridge_all_embed.py new file mode 100644 index 000000000..e3ea8a6f6 --- /dev/null +++ b/request_llms/embed_models/bridge_all_embed.py @@ -0,0 +1,40 @@ +import tiktoken, copy, re +from functools import lru_cache +from concurrent.futures import ThreadPoolExecutor +from toolbox import get_conf, trimmed_format_exc, apply_gpt_academic_string_mask, read_one_api_model_name + +# Endpoint 重定向 +API_URL_REDIRECT, AZURE_ENDPOINT, AZURE_ENGINE = get_conf("API_URL_REDIRECT", "AZURE_ENDPOINT", "AZURE_ENGINE") +openai_endpoint = "https://api.openai.com/v1/chat/completions" +if not AZURE_ENDPOINT.endswith('/'): AZURE_ENDPOINT += '/' +azure_endpoint = AZURE_ENDPOINT + f'openai/deployments/{AZURE_ENGINE}/chat/completions?api-version=2023-05-15' + + +if openai_endpoint in API_URL_REDIRECT: openai_endpoint = API_URL_REDIRECT[openai_endpoint] + +openai_embed_endpoint = openai_endpoint.replace("chat/completions", "embeddings") + +from .openai_embed import OpenAiEmbeddingModel + +embed_model_info = { + # text-embedding-3-small Increased performance over 2nd generation ada embedding model | 1,536 + "text-embedding-3-small": { + "embed_class": OpenAiEmbeddingModel, + "embed_endpoint": openai_embed_endpoint, + "embed_dimension": 1536, + }, + + # text-embedding-3-large Most capable embedding model for both english and non-english tasks | 3,072 + "text-embedding-3-large": { + "embed_class": OpenAiEmbeddingModel, + "embed_endpoint": openai_embed_endpoint, + "embed_dimension": 3072, + }, + + # text-embedding-ada-002 Most capable 2nd generation embedding model, replacing 16 first generation models | 1,536 + "text-embedding-ada-002": { + "embed_class": OpenAiEmbeddingModel, + "embed_endpoint": openai_embed_endpoint, + "embed_dimension": 1536, + }, +} diff --git a/request_llms/embed_models/openai_embed.py b/request_llms/embed_models/openai_embed.py new file mode 100644 index 000000000..9d565173d --- /dev/null +++ b/request_llms/embed_models/openai_embed.py @@ -0,0 +1,85 @@ +from llama_index.embeddings.openai import OpenAIEmbedding +from openai import OpenAI +from toolbox import get_conf +from toolbox import CatchException, update_ui, get_conf, select_api_key, get_log_folder, ProxyNetworkActivate +from shared_utils.key_pattern_manager import select_api_key_for_embed_models +from typing import List, Any + +import numpy as np + +def mean_agg(embeddings): + """Mean aggregation for embeddings.""" + return np.array(embeddings).mean(axis=0).tolist() + +class EmbeddingModel(): + + def get_agg_embedding_from_queries( + self, + queries: List[str], + agg_fn = None, + ): + """Get aggregated embedding from multiple queries.""" + query_embeddings = [self.get_query_embedding(query) for query in queries] + agg_fn = agg_fn or mean_agg + return agg_fn(query_embeddings) + + def get_text_embedding_batch( + self, + texts: List[str], + show_progress: bool = False, + ): + return self.compute_embedding(texts, batch_mode=True) + + +class OpenAiEmbeddingModel(EmbeddingModel): + + def __init__(self, llm_kwargs:dict=None): + self.llm_kwargs = llm_kwargs + + def get_query_embedding(self, query: str): + return self.compute_embedding(query) + + def compute_embedding(self, text="这是要计算嵌入的文本", llm_kwargs:dict=None, batch_mode=False): + from .bridge_all_embed import embed_model_info + + # load kwargs + if llm_kwargs is None: + llm_kwargs = self.llm_kwargs + if llm_kwargs is None: + raise RuntimeError("llm_kwargs is not provided!") + + # setup api and req url + api_key = select_api_key_for_embed_models(llm_kwargs['api_key'], llm_kwargs['embed_model']) + embed_model = llm_kwargs['embed_model'] + base_url = embed_model_info[llm_kwargs['embed_model']]['embed_endpoint'].replace('embeddings', '') + + # send and compute + with ProxyNetworkActivate("Connect_OpenAI_Embedding"): + self.oai_client = OpenAI(api_key=api_key, base_url=base_url) + if batch_mode: + input = text + assert isinstance(text, list) + else: + input = [text] + assert isinstance(text, str) + res = self.oai_client.embeddings.create(input=input, model=embed_model) + + # parse result + if batch_mode: + embedding = [d.embedding for d in res.data] + else: + embedding = res.data[0].embedding + return embedding + + def embedding_dimension(self, llm_kwargs=None): + # load kwargs + if llm_kwargs is None: + llm_kwargs = self.llm_kwargs + if llm_kwargs is None: + raise RuntimeError("llm_kwargs is not provided!") + + from .bridge_all_embed import embed_model_info + return embed_model_info[llm_kwargs['embed_model']]['embed_dimension'] + +if __name__ == "__main__": + pass \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f129dfe11..a05cc191f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,8 +6,9 @@ zhipuai==2.0.1 tiktoken>=0.7.0 requests[socks] pydantic==2.5.2 -protobuf==3.18 -transformers>=4.27.1 +llama-index==0.10 +protobuf==3.20 +transformers>=4.27.1,<4.42 scipdf_parser>=0.52 anthropic>=0.18.1 python-markdown-math diff --git a/shared_utils/config_loader.py b/shared_utils/config_loader.py index cf5d58cf3..3cfbd1d63 100644 --- a/shared_utils/config_loader.py +++ b/shared_utils/config_loader.py @@ -88,7 +88,7 @@ def read_single_conf_with_lru_cache(arg): if is_any_api_key(r): print亮绿(f"[API_KEY] 您的 API_KEY 是: {r[:15]}*** API_KEY 导入成功") else: - print亮红("[API_KEY] 您的 API_KEY 不满足任何一种已知的密钥格式,请在config文件中修改API密钥之后再运行。") + print亮红(f"[API_KEY] 您的 API_KEY({r[:15]}***)不满足任何一种已知的密钥格式,请在config文件中修改API密钥之后再运行(详见`https://github.com/binary-husky/gpt_academic/wiki/api_key`)。") if arg == 'proxies': if not read_single_conf_with_lru_cache('USE_PROXY'): r = None # 检查USE_PROXY,防止proxies单独起作用 if r is None: diff --git a/shared_utils/key_pattern_manager.py b/shared_utils/key_pattern_manager.py index 52366d796..44e383108 100644 --- a/shared_utils/key_pattern_manager.py +++ b/shared_utils/key_pattern_manager.py @@ -8,13 +8,20 @@ pj = os.path.join default_user_name = 'default_user' - +# match openai keys +openai_regex = re.compile( + r"sk-[a-zA-Z0-9_-]{48}$|" + + r"sk-[a-zA-Z0-9_-]{92}$|" + + r"sk-proj-[a-zA-Z0-9_-]{48}$|"+ + r"sk-proj-[a-zA-Z0-9_-]{124}$|"+ + r"sess-[a-zA-Z0-9]{40}$" +) def is_openai_api_key(key): CUSTOM_API_KEY_PATTERN = get_conf('CUSTOM_API_KEY_PATTERN') if len(CUSTOM_API_KEY_PATTERN) != 0: API_MATCH_ORIGINAL = re.match(CUSTOM_API_KEY_PATTERN, key) else: - API_MATCH_ORIGINAL = re.match(r"sk-[a-zA-Z0-9]{48}$|sk-proj-[a-zA-Z0-9]{48}$|sess-[a-zA-Z0-9]{40}$", key) + API_MATCH_ORIGINAL = openai_regex.match(key) return bool(API_MATCH_ORIGINAL) @@ -72,7 +79,7 @@ def select_api_key(keys, llm_model): return random.choice(key_list) - if llm_model.startswith('gpt-') or llm_model.startswith('one-api-') or llm_model.startswith('claude') or llm_model.startswith('local'): + if llm_model.startswith('gpt-') or llm_model.startswith('one-api-') or llm_model.startswith('o1-'): for k in key_list: if is_openai_api_key(k): avail_key_list.append(k) @@ -93,3 +100,19 @@ def select_api_key(keys, llm_model): api_key = random.choice(avail_key_list) # 随机负载均衡 return api_key + + +def select_api_key_for_embed_models(keys, llm_model): + import random + avail_key_list = [] + key_list = keys.split(',') + + if llm_model.startswith('text-embedding-'): + for k in key_list: + if is_openai_api_key(k): avail_key_list.append(k) + + if len(avail_key_list) == 0: + raise RuntimeError(f"您提供的api-key不满足要求,不包含任何可用于{llm_model}的api-key。您可能选择了错误的模型或请求源。") + + api_key = random.choice(avail_key_list) # 随机负载均衡 + return api_key diff --git a/tests/test_embed.py b/tests/test_embed.py new file mode 100644 index 000000000..956822fe2 --- /dev/null +++ b/tests/test_embed.py @@ -0,0 +1,586 @@ +def validate_path(): + import os, sys + os.path.dirname(__file__) + root_dir_assume = os.path.abspath(os.path.dirname(__file__) + "/..") + os.chdir(root_dir_assume) + sys.path.append(root_dir_assume) + +validate_path() # validate path so you can run from base directory + +# # """ +# # Test 1 +# # """ + +# # from request_llms.embed_models.openai_embed import OpenAiEmbeddingModel +# # from shared_utils.connect_void_terminal import get_chat_default_kwargs +# # oaiem = OpenAiEmbeddingModel() + +# # chat_kwargs = get_chat_default_kwargs() +# # llm_kwargs = chat_kwargs['llm_kwargs'] +# # llm_kwargs.update({ +# # 'llm_model': "text-embedding-3-small" +# # }) + +# # res = oaiem.compute_embedding("你好", llm_kwargs) +# # print(res) + +# """ +# Test 2 +# """ + +# from request_llms.embed_models.openai_embed import OpenAiEmbeddingModel +from shared_utils.connect_void_terminal import get_chat_default_kwargs +# from llama_index.core import VectorStoreIndex, SimpleDirectoryReader +# from crazy_functions.rag_fns.vector_store_index import GptacVectorStoreIndex +# from llama_index.core.ingestion import run_transformations +# from llama_index.core import PromptTemplate +# from llama_index.core.response_synthesizers import TreeSummarize + +# # NOTE: we add an extra tone_name variable here +# DEFAULT_QUESTION_GENERATION_PROMPT = """\ +# Context information is below. +# --------------------- +# {context_str} +# --------------------- +# Given the context information and not prior knowledge. +# generate only questions based on the below query. +# {query_str} +# """ + + +chat_kwargs = get_chat_default_kwargs() +llm_kwargs = chat_kwargs['llm_kwargs'] +llm_kwargs.update({ + 'llm_model': "text-embedding-3-small", + 'embed_model': "text-embedding-3-small" +}) +# embed_model = OpenAiEmbeddingModel(llm_kwargs) + +# ## dir +# documents = SimpleDirectoryReader("private_upload/rag_test/").load_data() + +# ## single files +# # from llama_index.core import Document +# # text_list = [text1, text2, ...] +# # documents = [Document(text=t) for t in text_list] +# vsi = GptacVectorStoreIndex.default_vector_store(embed_model=embed_model) +# documents_nodes = run_transformations( +# documents, # type: ignore +# vsi._transformations, +# show_progress=True +# ) +# index = vsi.insert_nodes(documents_nodes) +# retriever = vsi.as_retriever() + +# query = "what is core_functional.py" + +# res = retriever.retrieve(query) +# context_str = '\n'.join([r.text for r in res]) +# query_str = query +# query = DEFAULT_QUESTION_GENERATION_PROMPT.format(context_str=context_str, query_str=query_str) +# print(res) +# print(res) + + +# # response = query_engine.query("Some question about the data should go here") +# # print(response) + +from crazy_functions.rag_fns.llama_index_worker import LlamaIndexRagWorker +rag_worker = LlamaIndexRagWorker('good-man-user', llm_kwargs, checkpoint_dir='./longlong_vector_store') + +rag_worker.add_text_to_vector_store(""" +熊童子(Cotyledon tomentosa)是景天科,银波锦属的多年生肉质草本植物,植株多分枝,茎绿色,肉质叶肥厚,交互对生,卵圆形,绿色,密生白色短毛。叶端具红色爪样齿,二歧聚伞花序,小花黄色,花期7-9月。 +该种原产于南非开普省。喜温暖干燥,阳光充足,通风良好的环境。夏季温度过高会休眠。忌寒冷和过分潮湿。繁殖方法有扦插。 +该种叶形叶色较美,花朵玲珑小巧,叶片形似小熊的脚掌,形态奇特,十分可爱,观赏价值很高。 +物种索引:IN4679748 +""") + +rag_worker.add_text_to_vector_store(""" +碧光环是番杏科碧光玉属 [4]多年生肉质草本植物。 [5]碧光环叶表面有半透明的颗粒感,晶莹剔透;两片圆柱形的叶子,在生长初期像兔耳,非常可爱,长大后叶子会慢慢变长变粗,缺水时容易耷拉下来;具枝干,易群生。 +碧光环原产于南非。碧光环喜温暖和散射光充足的环境,较耐寒,忌强光暴晒,夏季高温休眠明显。 [6]碧光环的繁殖方式有扦插和播种。 [7] +碧光环小巧饱满、圆滚滚的样子很可爱,长得好像长耳朵小兔,萌萌的样子让人爱不释手,而且养起来也不难,极具观赏价值。 [8] +物种索引:IN985654 +""") + +rag_worker.add_text_to_vector_store(""" +福娘为景天科银波锦属的肉质草本植物。对生的叶片呈短棒状,叶色灰绿,表覆白粉,叶缘外围镶着紫红色,叶片外形多有变化有短圆形、厚厚的方形等不同叶形; [5]花期夏秋。 [6] +福娘原产于非洲西南部的纳米比亚,现世界多地均有栽培。性喜欢凉爽通风、日照充足的环境,较喜光照,喜肥,生长适温为15-25℃,冬季温度不低于5℃,生长期要见干见湿。 [7]在通风透气、排水良好的土壤上生长良好,一般可用泥炭土、蛭石和珍珠岩的混合土。繁殖方式一般为扦插繁殖,多用枝插,叶插的繁殖成功率不高。 [8] +因福娘的叶形叶色较美,所以具有一定的观赏价值,可盆栽放置于电视、电脑旁,吸收辐射,亦可栽植于室内以吸收甲醛等物质,净化空气。 [9] +物种索引:IN772 +""") + +rag_worker.add_text_to_vector_store(""" +石莲( Sinocrassula indica (Decne.) A. Berger)是景天科石莲属 [8]的二年生草本植物。基生叶莲座状,匙状长圆形;茎生叶互生,宽倒披针状线形或近倒卵形;花序圆锥状或近伞房状,萼片呈宽三角形,花瓣呈红色,披针形或卵形,雄蕊呈正方形;蓇葖果的喙反曲;种子平滑;花期9月;果期10月 [9]。锯叶石莲为石莲的变种,与原变种的不同处为叶上部有渐尖的锯齿。茎和花无毛,叶被毛 [10]。因叶子有棱有角,又似玉石,故而得名“石莲” [11]。 +物种索引:IN455674 +""") + +rag_worker.add_text_to_vector_store(""" +虹之玉锦(Sedum × rubrotinctum 'Aurora') [1]是景天科景天属的多肉植物,为虹之玉的锦化品种。虹之玉锦与虹之玉的叶片大小没有特别大的变化,但颜色会稍有不同,虹之玉锦一般会有粉红色、中绿色等 [2]。生长速度较虹之玉慢很多 [3]。 +物种索引:IN88 +""") + +rag_worker.add_text_to_vector_store(""" +一个幽灵,共产主义的幽灵,在欧洲游荡。为了对这个幽灵进行神圣的围剿,旧欧洲的一切势力,教皇和沙皇、梅特涅和基佐、法国的激进派和德国的警察,都联合起来了。 + +有哪一个反对党不被它的当政的敌人骂为共产党呢?又有哪一个反对党不拿共产主义这个罪名去回敬更进步的反对党人和自己的反动敌人呢? + +从这一事实中可以得出两个结论: + +共产主义已经被欧洲的一切势力公认为一种势力; + +现在是共产党人向全世界公开说明自己的观点、自己的目的、自己的意图并且拿党自己的宣言来反驳关于共产主义幽灵的神话的时候了。 + +为了这个目的,各国共产党人集会于伦敦,拟定了如下的宣言,用英文、法文、德文、意大利文、弗拉芒文和丹麦文公布于世。 + +一、资产者和无产者 + +至今一切社会的历史都是阶级斗争的历史。 + +自由民和奴隶、贵族和平民、领主和农奴、行会师傅和帮工,一句话,压迫者和被压迫者,始终处于相互对立的地位,进行不断的、有时隐蔽有时公开的斗争,而每一次斗争的结局都是整个社会受到革命改造或者斗争的各阶级同归于尽。 + +在过去的各个历史时代,我们几乎到处都可以看到社会完全划分为各个不同的等级,看到社会地位分成多种多样的层次。在古罗马,有贵族、骑士、平民、奴隶,在中世纪,有封建主、臣仆、行会师傅、帮工、农奴,而且几乎在每一个阶级内部又有一些特殊的阶层。 + +从封建社会的灭亡中产生出来的现代资产阶级社会并没有消灭阶级对立。它只是用新的阶级、新的压迫条件、新的斗争形式代替了旧的。 + +但是,我们的时代,资产阶级时代,却有一个特点:它使阶级对立简单化了。整个社会日益分裂为两大敌对的阵营,分裂为两大相互直接对立的阶级:资产阶级和无产阶级。 + +从中世纪的农奴中产生了初期城市的城关市民;从这个市民等级中发展出最初的资产阶级分子。 + +美洲的发现、绕过非洲的航行,给新兴的资产阶级开辟了新天地。东印度和中国的市场、美洲的殖民化、对殖民地的贸易、交换手段和一般商品的增加,使商业、航海业和工业空前高涨,因而使正在崩溃的封建社会内部的革命因素迅速发展。 + +以前那种封建的或行会的工业经营方式已经不能满足随着新市场的出现而增加的需求了。工场手工业代替了这种经营方式。行会师傅被工业的中间等级排挤掉了;各种行业组织之间的分工随着各个作坊内部的分工的出现而消失了。 + +但是,市场总是在扩大,需求总是在增加。甚至工场手工业也不再能满足需要了。于是,蒸汽和机器引起了工业生产的革命。现代大工业代替了工场手工业;工业中的百万富翁,一支一支产业大军的首领,现代资产者,代替了工业的中间等级。 + +大工业建立了由美洲的发现所准备好的世界市场。世界市场使商业、航海业和陆路交通得到了巨大的发展。这种发展又反过来促进了工业的扩展。同时,随着工业、商业、航海业和铁路的扩展,资产阶级也在同一程度上得到发展,增加自己的资本,把中世纪遗留下来的一切阶级排挤到后面去。 + +由此可见,现代资产阶级本身是一个长期发展过程的产物,是生产方式和交换方式的一系列变革的产物。 + +资产阶级的这种发展的每一个阶段,都伴随着相应的政治上的进展。它在封建主统治下是被压迫的等级,在公社里是武装的和自治的团体,在一些地方组成独立的城市共和国,在另一些地方组成君主国中的纳税的第三等级;后来,在工场手工业时期,它是等级君主国或专制君主国中同贵族抗衡的势力,而且是大君主国的主要基础;最后,从大工业和世界市场建立的时候起,它在现代的代议制国家里夺得了独占的政治统治。现代的国家政权不过是管理整个资产阶级的共同事务的委员会罢了。 + +资产阶级在历史上曾经起过非常革命的作用。 + +资产阶级在它已经取得了统治的地方把一切封建的、宗法的和田园般的关系都破坏了。它无情地斩断了把人们束缚于天然尊长的形形色色的封建羁绊,它使人和人之间除了赤裸裸的利害关系,除了冷酷无情的“现金交易”,就再也没有任何别的联系了。它把宗教虔诚、骑士热忱、小市民伤感这些情感的神圣发作,淹没在利己主义打算的冰水之中。它把人的尊严变成了交换价值,用一种没有良心的贸易自由代替了无数特许的和自力挣得的自由。总而言之,它用公开的、无耻的、直接的、露骨的剥削代替了由宗教幻想和政治幻想掩盖着的剥削。 + +资产阶级抹去了一切向来受人尊崇和令人敬畏的职业的神圣光环。它把医生、律师、教士、诗人和学者变成了它出钱招雇的雇佣劳动者。 + +资产阶级撕下了罩在家庭关系上的温情脉脉的面纱,把这种关系变成了纯粹的金钱关系。 + +资产阶级揭示了,在中世纪深受反动派称许的那种人力的野蛮使用,是以极端怠惰作为相应补充的。它第一个证明了,人的活动能够取得什么样的成就。它创造了完全不同于埃及金字塔、罗马水道和哥特式教堂的奇迹;它完成了完全不同于民族大迁徙和十字军征讨的远征。 + +资产阶级除非对生产工具,从而对生产关系,从而对全部社会关系不断地进行革命,否则就不能生存下去。反之,原封不动地保持旧的生产方式,却是过去的一切工业阶级生存的首要条件。生产的不断变革,一切社会状况不停的动荡,永远的不安定和变动,这就是资产阶级时代不同于过去一切时代的地方。一切固定的僵化的关系以及与之相适应的素被尊崇的观念和见解都被消除了,一切新形成的关系等不到固定下来就陈旧了。一切等级的和固定的东西都烟消云散了,一切神圣的东西都被亵渎了。人们终于不得不用冷静的眼光来看他们的生活地位、他们的相互关系。 + +不断扩大产品销路的需要,驱使资产阶级奔走于全球各地。它必须到处落户,到处开发,到处建立联系。 + +资产阶级,由于开拓了世界市场,使一切国家的生产和消费都成为世界性的了。使反动派大为惋惜的是,资产阶级挖掉了工业脚下的民族基础。古老的民族工业被消灭了,并且每天都还在被消灭。它们被新的工业排挤掉了,新的工业的建立已经成为一切文明民族的生命攸关的问题;这些工业所加工的,已经不是本地的原料,而是来自极其遥远的地区的原料;它们的产品不仅供本国消费,而且同时供世界各地消费。旧的、靠本国产品来满足的需要,被新的、要靠极其遥远的国家和地带的产品来满足的需要所代替了。过去那种地方的和民族的自给自足和闭关自守状态,被各民族的各方面的互相往来和各方面的互相依赖所代替了。物质的生产是如此,精神的生产也是如此。各民族的精神产品成了公共的财产。民族的片面性和局限性日益成为不可能,于是由许多种民族的和地方的文学形成了一种世界的文学。 + +资产阶级,由于一切生产工具的迅速改进,由于交通的极其便利,把一切民族甚至最野蛮的民族都卷到文明中来了。它的商品的低廉价格,是它用来摧毁一切万里长城、征服野蛮人最顽强的仇外心理的重炮。它迫使一切民族——如果它们不想灭亡的话——采用资产阶级的生产方式;它迫使它们在自己那里推行所谓的文明,即变成资产者。一句话,它按照自己的面貌为自己创造出一个世界。 + +资产阶级使农村屈服于城市的统治。它创立了巨大的城市,使城市人口比农村人口大大增加起来,因而使很大一部分居民脱离了农村生活的愚昧状态。正像它使农村从属于城市一样,它使未开化和半开化的国家从属于文明的国家,使农民的民族从属于资产阶级的民族,使东方从属于西方。 + +资产阶级日甚一日地消灭生产资料、财产和人口的分散状态。它使人口密集起来,使生产资料集中起来,使财产聚集在少数人的手里。由此必然产生的结果就是政治的集中。各自独立的、几乎只有同盟关系的、各有不同利益、不同法律、不同政府、不同关税的各个地区,现在已经结合为一个拥有统一的政府、统一的法律、统一的民族阶级利益和统一的关税的统一的民族。 + +资产阶级在它的不到一百年的阶级统治中所创造的生产力,比过去一切世代创造的全部生产力还要多,还要大。自然力的征服,机器的采用,化学在工业和农业中的应用,轮船的行驶,铁路的通行,电报的使用,整个整个大陆的开垦,河川的通航,仿佛用法术从地下呼唤出来的大量人口,——过去哪一个世纪料想到在社会劳动里蕴藏有这样的生产力呢? + +由此可见,资产阶级赖以形成的生产资料和交换手段,是在封建社会里造成的。在这些生产资料和交换手段发展的一定阶段上,封建社会的生产和交换在其中进行的关系,封建的农业和工场手工业组织,一句话,封建的所有制关系,就不再适应已经发展的生产力了。这种关系已经在阻碍生产而不是促进生产了。它变成了束缚生产的桎梏。它必须被炸毁,它已经被炸毁了。 + +起而代之的是自由竞争以及与自由竞争相适应的社会制度和政治制度、资产阶级的经济统治和政治统治。 + +现在,我们眼前又进行着类似的运动。资产阶级的生产关系和交换关系,资产阶级的所有制关系,这个曾经仿佛用法术创造了如此庞大的生产资料和交换手段的现代资产阶级社会,现在像一个魔法师一样不能再支配自己用法术呼唤出来的魔鬼了。几十年来的工业和商业的历史,只不过是现代生产力反抗现代生产关系、反抗作为资产阶级及其统治的存在条件的所有制关系的历史。只要指出在周期性的重复中越来越危及整个资产阶级社会生存的商业危机就够了。在商业危机期间,总是不仅有很大一部分制成的产品被毁灭掉,而且有很大一部分已经造成的生产力被毁灭掉。在危机期间,发生一种在过去一切时代看来都好像是荒唐现象的社会瘟疫,即生产过剩的瘟疫。社会突然发现自己回到了一时的野蛮状态;仿佛是一次饥荒、一场普遍的毁灭性战争,使社会失去了全部生活资料;仿佛是工业和商业全被毁灭了,——这是什么缘故呢?因为社会上文明过度,生活资料太多,工业和商业太发达。社会所拥有的生产力已经不能再促进资产阶级文明和资产阶级所有制关系的发展;相反,生产力已经强大到这种关系所不能适应的地步,它已经受到这种关系的阻碍;而它一着手克服这种障碍,就使整个资产阶级社会陷入混乱,就使资产阶级所有制的存在受到威胁。资产阶级的关系已经太狭窄了,再容纳不了它本身所造成的财富了。——资产阶级用什么办法来克服这种危机呢?一方面不得不消灭大量生产力,另一方面夺取新的市场,更加彻底地利用旧的市场。这究竟是怎样的一种办法呢?这不过是资产阶级准备更全面更猛烈的危机的办法,不过是使防止危机的手段越来越少的办法。 + +资产阶级用来推翻封建制度的武器,现在却对准资产阶级自己了。 + +但是,资产阶级不仅锻造了置自身于死地的武器;它还产生了将要运用这种武器的人——现代的工人,即无产者。 + +随着资产阶级即资本的发展,无产阶级即现代工人阶级也在同一程度上得到发展;现代的工人只有当他们找到工作的时候才能生存,而且只有当他们的劳动增殖资本的时候才能找到工作。这些不得不把自己零星出卖的工人,像其他任何货物一样,也是一种商品,所以他们同样地受到竞争的一切变化、市场的一切波动的影响。 + +由于推广机器和分工,无产者的劳动已经失去了任何独立的性质,因而对工人也失去了任何吸引力。工人变成了机器的单纯的附属品,要求他做的只是极其简单、极其单调和极容易学会的操作。因此,花在工人身上的费用,几乎只限于维持工人生活和延续工人后代所必需的生活资料。但是,商品的价格,从而劳动的价格,是同它的生产费用相等的。因此,劳动越使人感到厌恶,工资也就越少。不仅如此,机器越推广,分工越细致,劳动量出就越增加,这或者是由于工作时间的延长,或者是由于在一定时间内所要求的劳动的增加,机器运转的加速,等等。 + +现代工业已经把家长式的师傅的小作坊变成了工业资本家的大工厂。挤在工厂里的工人群众就像士兵一样被组织起来。他们是产业军的普通士兵,受着各级军士和军官的层层监视。他们不仅仅是资产阶级的、资产阶级国家的奴隶,他们每日每时都受机器、受监工、首先是受各个经营工厂的资产者本人的奴役。这种专制制度越是公开地把营利宣布为自己的最终目的,它就越是可鄙、可恨和可恶。 + +手的操作所要求的技巧和气力越少,换句话说,现代工业越发达,男工也就越受到女工和童工的排挤。对工人阶级来说,性别和年龄的差别再没有什么社会意义了。他们都只是劳动工具,不过因为年龄和性别的不同而需要不同的费用罢了。 + +当厂主对工人的剥削告一段落,工人领到了用现钱支付的工资的时候,马上就有资产阶级中的另一部分人——房东、小店主、当铺老板等等向他们扑来。 + +以前的中间等级的下层,即小工业家、小商人和小食利者,手工业者和农民——所有这些阶级都降落到无产阶级的队伍里来了,有的是因为他们的小资本不足以经营大工业,经不起较大的资本家的竞争;有的是因为他们的手艺已经被新的生产方法弄得不值钱了。无产阶级就是这样从居民的所有阶级中得到补充的。 + +无产阶级经历了各个不同的发展阶段。它反对资产阶级的斗争是和它的存在同时开始的。 + +最初是单个的工人,然后是某一工厂的工人,然后是某一地方的某一劳动部门的工人,同直接剥削他们的单个资产者作斗争。他们不仅仅攻击资产阶级的生产关系,而且攻击生产工具本身;他们毁坏那些来竞争的外国商品,捣毁机器,烧毁工厂,力图恢复已经失去的中世纪工人的地位。 + +在这个阶段上,工人是分散在全国各地并为竞争所分裂的群众。工人的大规模集结,还不是他们自己联合的结果,而是资产阶级联合的结果,当时资产阶级为了达到自己的政治目的必须而且暂时还能够把整个无产阶级发动起来。因此,在这个阶段上,无产者不是同自己的敌人作斗争,而是同自己的敌人的敌人作斗争,即同专制君主制的残余、地主、非工业资产者和小资产者作斗争。因此,整个历史运动都集中在资产阶级手里;在这种条件下取得的每一个胜利都是资产阶级的胜利。 + +但是,随着工业的发展,无产阶级不仅人数增加了,而且它结合成更大的集体,它的力量日益增长,它越来越感觉到自己的力量。机器使劳动的差别越来越小,使工资几乎到处都降到同样低的水平,因而无产阶级内部的利益、生活状况也越来越趋于一致。资产者彼此间日益加剧的竞争以及由此引起的商业危机,使工人的工资越来越不稳定;机器的日益迅速的和继续不断的改良,使工人的整个生活地位越来越没有保障;单个工人和单个资产者之间的冲突越来越具有两个阶级的冲突的性质。工人开始成立反对资产者的同盟;他们联合起来保卫自己的工资。他们甚至建立了经常性的团体,以便为可能发生的反抗准备食品。有些地方,斗争爆发为起义。 + +工人有时也得到胜利,但这种胜利只是暂时的。他们斗争的真正成果并不是直接取得的成功,而是工人的越来越扩大的联合。这种联合由于大工业所造成的日益发达的交通工具而得到发展,这种交通工具把各地的工人彼此联系起来。只要有了这种联系,就能把许多性质相同的地方性的斗争汇合成全国性的斗争,汇合成阶级斗争。而一切阶级斗争都是政治斗争。中世纪的市民靠乡间小道需要几百年才能达到的联合,现代的无产者利用铁路只要几年就可以达到了。 + +无产者组织成为阶级,从而组织成为政党这件事,不断地由于工人的自相竞争而受到破坏。但是,这种组织总是重新产生,并且一次比一次更强大,更坚固,更有力。它利用资产阶级内部的分裂,迫使他们用法律形式承认工人的个别利益。英国的十小时工作日法案就是一个例子。 + +旧社会内部的所有冲突在许多方面都促进了无产阶级的发展。资产阶级处于不断的斗争中:最初反对贵族;后来反对同工业进步有利害冲突的那部分资产阶级;经常反对一切外国的资产阶级。在这一切斗争中,资产阶级都不得不向无产阶级呼吁,要求无产阶级援助,这样就把无产阶级卷进了政治运动。于是,资产阶级自己就把自己的教育因素即反对自身的武器给予了无产阶级。 + +其次,我们已经看到,工业的进步把统治阶级的整批成员抛到无产阶级队伍里去,或者至少也使他们的生活条件受到威胁。他们也给无产阶级带来了大量的教育因素。 + +最后,在阶级斗争接近决战的时期,统治阶级内部的、整个旧社会内部的瓦解过程,就达到非常强烈、非常尖锐的程度,甚至使得统治阶级中的一小部分人脱离统治阶级而归附于革命的阶级,即掌握着未来的阶级。所以,正像过去贵族中有一部分人转到资产阶级方面一样,现在资产阶级中也有一部分人,特别是已经提高到从理论上认识整个历史运动这一水平的一部分资产阶级思想家,转到无产阶级方面来了。 + +在当前同资产阶级对立的一切阶级中,只有无产阶级是真正革命的阶级。其余的阶级都随着大工业的发展而日趋没落和灭亡,无产阶级却是大工业本身的产物。 + +中间等级,即小工业家、小商人、手工业者、农民,他们同资产阶级作斗争,都是为了维护他们这种中间等级的生存,以免于灭亡。所以,他们不是革命的,而是保守的。不仅如此,他们甚至是反动的,因为他们力图使历史的车轮倒转。如果说他们是革命的,那是鉴于他们行将转入无产阶级的队伍,这样,他们就不是维护他们目前的利益,而是维护他们将来的利益,他们就离开自己原来的立场,而站到无产阶级的立场上来。 + +流氓无产阶级是旧社会最下层中消极的腐化的部分,他们在一些地方也被无产阶级革命卷到运动里来,但是,由于他们的整个生活状况,他们更甘心于被人收买,去干反动的勾当。 + +在无产阶级的生活条件中,旧社会的生活条件已经被消灭了。无产者是没有财产的;他们和妻子儿女的关系同资产阶级的家庭关系再没有任何共同之处了;现代的工业劳动,现代的资本压迫,无论在英国或法国,无论在美国或德国,都有是一样的,都使无产者失去了任何民族性。法律、道德、宗教在他们看来全都是资产阶级偏见,隐藏在这些偏见后面的全都是资产阶级利益。 + +过去一切阶级在争得统治之后,总是使整个社会服从于它们发财致富的条件,企图以此来巩固它们已获得的生活地位。无产者只有废除自己的现存的占有方式,从而废除全部现存的占有方式,才能取得社会生产力。无产者没有什么自己的东西必须加以保护,他们必须摧毁至今保护和保障私有财产的一切。 + +过去的一切运动都是少数人的或者为少数人谋利益的运动。无产阶级的运动是绝大多数人的、为绝大多数人谋利益的独立的运动。无产阶级,现今社会的最下层,如果不炸毁构成官方社会的整个上层,就不能抬起头来,挺起胸来。 + +如果不就内容而就形式来说,无产阶级反对资产阶级的斗争首先是一国范围内的斗争。每一个国家的无产阶级当然首先应该打倒本国的资产阶级。 + +在叙述无产阶级发展的最一般的阶段的时候,我们循序探讨了现存社会内部或多或少隐蔽着的国内战争,直到这个战争爆发为公开的革命,无产阶级用暴力推翻资产阶级而建立自己的统治。 + +我们已经看到,至今的一切社会都是建立在压迫阶级和被压迫阶级的对立之上的。但是,为了有可能压迫一个阶级,就必须保证这个阶级至少有能够勉强维持它的奴隶般的生存的条件。农奴曾经在农奴制度下挣扎到公社成员的地位,小资产者曾经在封建专制制度的束缚下挣扎到资产者的地位。现代的工人却相反,他们并不是随着工业的进步而上升,而是越来越降到本阶级的生存条件以下。工人变成赤贫者,贫困比人口和财富增长得还要快。由此可以明显地看出,资产阶级再不能做社会的统治阶级了,再不能把自己阶级的生存条件当作支配一切的规律强加于社会了。资产阶级不能统治下去了,因为它甚至不能保证自己的奴隶维持奴隶的生活,因为它不得不让自己的奴隶落到不能养活它反而要它来养活的地步。社会再不能在它统治下生存下去了,就是说,它的生存不再同社会相容了。 + +资产阶级生存和统治的根本条件,是财富在私人手里的积累,是资本的形成和增殖;资本的条件是雇佣劳动。雇佣劳动完全是建立在工人的自相竞争之上的。资产阶级无意中造成而又无力抵抗的工业进步,使工人通过结社而达到的革命联合代替了他们由于竞争而造成的分散状态。于是,随着大工业的发展,资产阶级赖以生产和占有产品的基础本身也就从它的脚下被挖掉了。它首先生产的是它自身的掘墓人。资产阶级的灭亡和无产阶级的胜利是同样不可避免的。 + +二、无产者和共产党人 + +共产党人同全体无产者的关系是怎样的呢? + +共产党人不是同其他工人政党相对立的特殊政党。 + +他们没有任何同整个无产阶级的利益不同的利益。 + +他们不提出任何特殊的原则,用以塑造无产阶级的运动。 + +共产党人同其他无产阶级政党不同的地方只是:一方面,在无产者不同的民族的斗争中,共产党人强调和坚持整个无产阶级共同的不分民族的利益;另一方面,在无产阶级和资产阶级的斗争所经历的各个发展阶段上,共产党人始终代表整个运动的利益。 + +因此,在实践方面,共产党人是各国工人政党中最坚决的、始终起推动作用的部分;在理论方面,他们胜过其余无产阶级群众的地方在于他们了解无产阶级运动的条件、进程和一般结果。 + +共产党人的最近目的是和其他一切无产阶级政党的最近目的一样的:使无产阶级形成为阶级,推翻资产阶级的统治,由无产阶级夺取政权。 + +共产党人的理论原理,决不是以这个或那个世界改革家所发明或发现的思想、原则为根据的。 + +这些原理不过是现存的阶级斗争、我们眼前的历史运动的真实关系的一般表述。废除先前存在的所有制关系,并不是共产主义所独具的特征。 + +一切所有制关系都经历了经常的历史更替、经常的历史变更。 + +例如,法国革命废除了封建的所有制,代之以资产阶级的所有制。 + +共产主义的特征并不是要废除一般的所有制,而是要废除资产阶级的所有制。 + +但是,现代的资产阶级私有制是建立在阶级对立上面、建立在一些人对另一些人的剥削上面的产品生产和占有的最后而又完备的表现。 + +从这个意义上说,共产党人可以把自己的理论概括为一句话:消灭私有制。 + +有人责备我们共产党人,说我们消灭个人挣得的、自己劳动得来的财产,要消灭构成个人的一切自由、活动和独立的基础的财产。 + +好一个劳动得来的、自己挣得的、自己赚来的财产!你们说的是资产阶级财产出现以前的那种小资产阶级、小农的财产吗?那种财产用不着我们去消灭,工业的发展已经把它消灭了,而且每天都在消灭它。 + +或者,你们说的是现代的资产阶级的私有财产吧? + +但是,难道雇佣劳动,无产者的劳动,会给无产者创造出财产来吗?没有的事。这种劳动所创造的资本,即剥削雇佣劳动的财产,只有在不断产生出新的雇佣劳动来重新加以剥削的条件下才能增殖的财产。现今的这种财产是在资本和雇佣劳动的对立中运动的。让我们来看看这种对立的两个方面吧。 + +做一个资本家,这就是说,他在生产中不仅占有一种纯粹个人的地位,而且占有一种社会地位。资本是集体的产物,它只有通过社会许多成员的共同活动,而且归根到底只有通过社会全体成员的共同活动,才能运动起来。 + +因此,资本不是一种个人力量,而是一种社会力量。 + +因此,把资本变为公共的、属于社会全体成员的财产,这并不是把个人财产变为社会财产。这里所改变的只是财产的社会性质。它将失掉它的阶级性质。 + +现在,我们来看看雇佣劳动。 + +雇佣劳动的平均价格是最低限度的工资,即工人为维持其工人的生活所必需的生活资料的数额。因此,雇佣工人靠自己的劳动所占有的东西,只够勉强维持他的生命的再生产。我们决不打算消灭这种供直接生命再生产用的劳动产品的个人占有,这种占有并不会留下任何剩余的东西使人们有可能支配别人的劳动。我们要消灭的只是这种占有的可怜的性质,在这种占有下,工人仅仅为增殖资本而活着,只有在统治阶级的利益需要他活着的时候才能活着。 + +在资产阶级社会里,活的劳动只是增殖已经积累起来的劳动的一种手段。在共产主义社会里,已经积累起来的劳动只是扩大、丰富和提高工人的生活的一种手段。 + +因此,在资产阶级社会里是过去支配现在,在共产主义社会里是现在支配过去。在资产阶级社会里,资本具有独立性和个性,而活动着的个人却没有独立性和个性。 + +而资产阶级却把消灭这种关系说成是消灭个性和自由!说对了。的确,正是要消灭资产者的个性、独立性和自由。 + +在现今的资产阶级生产关系的范围内,所谓自由就是自由贸易,自由买卖。 + +但是,买卖一消失,自由买卖也就会消失。关于自由买卖的言论,也像我们的资产阶级的其他一切关于自由的大话一样,仅仅对于不自由的买卖来说,对于中世纪被奴役的市民来说,才是有意义的,而对于共产主义要消灭买卖、消灭资产阶级生产关系和资产阶级本身这一点来说,却是毫无意义的。 + +我们要消灭私有制,你们就惊慌起来。但是,在你们的现存社会里,私有财产对十分之九的成员来说已经被消灭了;这种私有制这所以存在,正是因为私有财产对十分之九的成员来说已经不存在。可见,你们责备我们,是说我们要消灭那种以社会上的绝大多数人没有财产为必要条件的所有制。 + +总而言之,你们责备我们,是说我们要消灭你们的那种所有制。的确,我们是要这样做的。 + +从劳动不再能变为资本、货币、地租,一句话,不再能变为可以垄断的社会力量的时候起,就是说,从个人财产不再能变为资产阶级财产的时候起,你们说,个性被消灭了。 + +由此可见,你们是承认,你们所理解的个性,不外是资产者、资产阶级私有者。这样的个性确实应当被消灭。 + +共产主义并不剥夺任何人占有社会产品的权力,它只剥夺利用这种占有去奴役他人劳动的权力。 + +有人反驳说,私有制一消灭,一切活动就会停止,懒惰之风就会兴起。 + +这样说来,资产阶级社会早就应该因懒惰而灭亡了,因为在这个社会里劳者不获,获者不劳。所有这些顾虑,都可以归结为这样一个同义反复:一旦没有资本,也就不再有雇佣劳动了。 + +所有这些对共产主义的物质产品的占有方式和生产方式的责备,也被扩及到精神产品的占有和生产方面。正如阶级的所有制的终止在资产者看来是生产本身的终止一样,阶级的教育的终止在他们看来就等于一切教育的终止。 + +资产者唯恐失去的那种教育,绝大多数人来说是把人训练成机器。 + +但是,你们既然用你们资产阶级关于自由、教育、法等等的观念来衡量废除资产阶级所有制的主张,那就请你们不要同我们争论了。你们的观念本身是资产阶级的生产关系和所有制关系的产物,正像你们的法不过是被奉为法律的你们这个阶级的意志一样,而这种意志的内容是由你们这个阶级的物质生活条件决定的。 + +你们的利己观念使你们把自己的生产关系和所有制关系从历史的、在生产过程中是暂时的关系变成永恒的自然规律和理性规律,这种利己观念是你们和一切灭亡了的统治阶级所共有的。谈到古代所有制的时候你们所能理解的,谈到封建所有制的时候你们所能理解的,一谈到资产阶级所有制你们就再也不能理解了。 + +消灭家庭!连极端的激进派也对共产党人的这种可耻的意图表示愤慨。 + +现代的、资产阶级的家庭是建立在什么基础上的呢?是建立在资本上面,建立在私人发财上面的。这种家庭只是在资产阶级那里才以充分发展的形式存在着,而无产者的被迫独居和公开的卖淫则是它的补充。 + +资产者的家庭自然会随着它的这种补充的消失而消失,两者都要随着资本的消失而消失。 + +你们是责备我们要消灭父母对子女的剥削吗?我们承认这种罪状。 + +但是,你们说,我们用社会教育代替家庭教育,就是要消灭人们最亲密的关系。 + +而你们的教育不也是由社会决定的吗?不也是由你们进行教育时所处的那种社会关系决定的吗?不也是由社会通过学校等等进行的直接的或间接的干涉决定的吗?共产党人并没有发明社会对教育的作用;他们仅仅是要改变这种作用的性质,要使教育摆脱统治阶级的影响。 + +无产者的一切家庭联系越是由于大工业的发展而被破坏,他们的子女越是由于这种发展而被变成单纯的商品和劳动工具,资产阶级关于家庭和教育、关于父母和子女的亲密关系的空话就越是令人作呕。 + +但是,你们共产党人是要实行公妻制的啊,——整个资产阶级异口同声地向我们这样叫喊。 + +资产者是把自己的妻子看作单纯的生产工具的。他们听说生产工具将要公共使用,自然就不能不想到妇女也会遭到同样的命运。 + +他们想也没有想到,问题正在于使妇女不再处于单纯生产工具的地位。 + +其实,我们的资产者装得道貌岸然,对所谓的共产党人的正式公妻制表示惊讶,那是再可笑不过了。公妻制无需共产党人来实行,它差不多是一向就有的。 + +我们的资产者不以他们的无产者的妻子和女儿受他们支配为满足,正式的卖淫更不必说了,他们还以互相诱奸妻子为最大的享乐。 + +资产阶级的婚姻实际上是公妻制。人们至多只能责备共产党人,说他们想用正式的、公开的公妻制来代替伪善地掩蔽着的公妻制。其实,不言而喻,随着现在的生产关系的消灭,从这种关系中产生的公妻制,即正式的和非正式的卖淫,也就消失了。 + +有人还责备共产党人,说他们要取消祖国,取消民族。 + +工人没有祖国。决不能剥夺他们所没有的东西。因为无产阶级首先必须取得政治统治,上升为民族的阶级,把自身组织成为民族,所以它本身还是民族的,虽然完全不是资产阶级所理解的那种意思。 + +随着资产阶级的发展,随着贸易自由的实现和世界市场的建立,随着工业生产以及与之相适应的生活条件的趋于一致,各国人民之间的民族分隔和对立日益消失。 + +无产阶级的统治将使它们更快地消失。联合的行动,至少是各文明国家的联合的行动,是无产阶级获得解放的首要条件之一。 + +人对人的剥削一消灭,民族对民族的剥削就会随之消灭。 + +民族内部的阶级对立一消失,民族之间的敌对关系就会随之消失。 + +从宗教的、哲学的和一切意识形态的观点对共产主义提出的种种责难,都不值得详细讨论了。 + +人们的观念、观点和概念,一句话,人们的意识,随着人们的生活条件、人们的社会关系、人们的社会存在的改变而改变,这难道需要经过深思才能了解吗? + +思想的历史除了证明精神生产随着物质生产的改造而改造,还证明了什么呢?任何一个时代的统治思想始终都不过是统治阶级的思想。 + +当人们谈到使整个社会革命化的思想时,他们只是表明了一个事实:在旧社会内部已经形成了新社会的因素,旧思想的瓦解是同旧生活条件的瓦解步调一致的。 + +当古代世界走向灭亡的时候,古代的各种宗教就被基督教战胜了。当基督教思想在18世纪被启蒙思想击败的时候,封建社会正在同当时革命的资产阶级进行殊死的斗争。信仰自由和宗教自由的思想,不过表明竞争在信仰领域里占统治地位罢了。 + +“但是”,有人会说,“宗教的、道德的、哲学的、政治的、法的观念等等在历史发展的进程中固然是不断改变的,而宗教、道德、哲学、政治和法在这种变化中却始终保存着。 + +此外,还存在着一切社会状态所共有的永恒真理,如自由、正义等等。但是共产主义要废除永恒真理,它要废除宗教、道德,而不是加以革新,所以共产主义是同至今的全部历史发展相矛盾的。” + +这种责难归结为什么呢?至今的一切社会的历史都是在阶级对立中运动的,而这种对立在不同的时代具有不同的形式。 + +但是,不管阶级对立具有什么样的形式,社会上一部分人对另一部分人的剥削却是过去各个世纪所共有的事实。因此,毫不奇怪,各个世纪的社会意识,尽管形形色色、千差万别,总是在某些共同的形式中运动的,这些形式,这些意识形式,只有当阶级对立完全消失的时候才会完全消失。 + +共产主义革命就是同传统的所有制关系实行最彻底的决裂;毫不奇怪,它在自己的发展进程中要同传统的观念实行最彻底的决裂。 + +不过,我们还是把资产阶级对共产主义的种种责难撇开吧。 + +前面我们已经看到,工人革命的第一步就是使无产阶级上升为统治阶级,争得民主。 + +无产阶级将利用自己的政治统治,一步一步地夺取资产阶级的全部资本,把一切生产工具集中在国家即组织成为统治阶级的无产阶级手里,并且尽可能快地增加生产力的总量。 + +要做到这一点,当然首先必须对所有权和资产阶级生产关系实行强制性的干涉,也就是采取这样一些措施,这些措施在经济上似乎是不够充分的和没有力量的,但是在运动进程中它们会越出本身,而且作为变革全部生产方式的手段是必不可少的。 + +这些措施在不同的国家里当然会是不同的。 + +但是,最先进的国家几乎都可以采取下面的措施: + +1、剥夺地产,把地租用于国家支出。 + +2、征收高额累进税。 + +3、废除继承权。 + +4、没收一切流亡分子和叛乱分子的财产。 + +5、通过拥有国家资本和独享垄断权的国家银行,把信贷集中在国家手里。 + +6、把全部运输业集中在国家的手里。 + +7、按照总的计划增加国家工厂和生产工具,开垦荒地和改良土壤。 + +8、实行普遍劳动义务制,成立产业军,特别是在农业方面。 + +9、把农业和工业结合起来,促使城乡对立逐步消灭。 + +10、对所有儿童实行公共的和免费的教育。取消现在这种形式的儿童的工厂劳动。把教育同物质生产结合起来,等等。 + +当阶级差别在发展进程中已经消失而全部生产集中在联合起来的个人的手里的时候,公共权力就失去政治性质。原来意义上的政治权力,是一个阶级用以压迫另一个阶级的有组织的暴力。如果说无产阶级在反对资产阶级的斗争中一定要联合为阶级,如果说它通过革命使自己成为统治阶级,并以统治阶级的资格用暴力消灭旧的生产关系,那么它在消灭这种生产关系的同时,也就消灭了阶级对立的存在条件,消灭阶级本身的存在条件,从而消灭了它自己这个阶级的统治。 + +代替那存在着阶级和阶级对立的资产阶级旧社会的,将是这样一个联合体,在那里,每个人的自由发展是一切人的自由发展的条件。 + +三、社会主义的和共产主义的文献 + +1.反动的社会主义 + +(甲)封建的社会主义 + +法国和英国的贵族,按照他们的历史地位所负的使命,就是写一些抨击现代资产阶级社会的作品。在法国的1830年七月革命和英国的改革运动 中,他们再一次被可恨的暴发户打败了。从此就再谈不上严重的政治斗争了。他们还能进行的只是文字斗争。但是,即使在文字方面也不可能重弹复辟时期的老调了。为了激起同情,贵族们不得不装模作样,似乎他们已经不关心自身的利益,只是为了被剥削的工人阶级的利益才去写对资产阶级的控诉书。他们用来泄愤的手段是:唱唱诅咒他们的新统治者的歌,并向他叽叽咕咕地说一些或多或少凶险的预言。 + +这样就产生了封建的社会主义,半是挽歌,半是谤文,半是过去的回音,半是未来的恫吓;它有时也能用辛辣、俏皮而尖刻的评论剌中资产阶级的心,但是它由于完全不能理解现代历史的进程而总是令人感到可笑。 + +为了拉拢人民,贵族们把无产阶级的乞食袋当作旗帜来挥舞。但是,每当人民跟着他们走的时候,都发现他们的臀部带有旧的封建纹章,于是就哈哈大笑,一哄而散。 + +一部分法国正统派和“青年英国”,都演过这出戏。 + +封建主说,他们的剥削方式和资产阶级的剥削不同,那他们只是忘记了,他们是在完全不同的、目前已经过时的情况和条件下进行剥削的。他们说,在他们的统治下并没有出现过现代的无产阶级,那他们只是忘记了,现代的资产阶级正是他们的社会制度的必然产物。 + +不过,他们毫不掩饰自己的批评的反动性质,他们控告资产阶级的主要罪状正是在于:在资产阶级的统治下有一个将把整个旧社会制度炸毁的阶级发展起来。 + +他们责备资产阶级,与其说是因为它产生了无产阶级,不如说是因为它产生了革命的无产阶级。 + +因此,在政治实践中,他们参与对工人阶级采取的一切暴力措施,在日常生活中,他们违背自己的那一套冠冕堂皇的言词,屈尊拾取金苹果,不顾信义、仁爱和名誉去做羊毛、甜菜和烧洒的买卖。 + +正如僧侣总是同封建主携手同行一样,僧侣的社会主义也总是同封建的社会主义携手同行的。 + +要给基督教禁欲主义涂上一层社会主义的色彩,是再容易不过了。基督教不是也激烈反对私有财产,反对婚姻,反对国家吗?它不是提倡用行善和求乞、独身和禁欲、修道和礼拜来代替这一切吗?基督教的社会主义,只不过是僧侣用来使贵族的怨愤神圣的圣水罢了。 + +(乙)小资产阶级的社会主义 + +封建贵族并不是被资产阶级所推翻的、其生活条件在现代资产阶级社会里日益恶化和消失的唯一阶级。中世纪的城关市民和小农等级是现代资产阶级的前身。在工商业不很发达的国家里,这个阶级还在新兴的资产阶级身旁勉强生存着。 + +在现代文明已经发展的国家里,形成了一个新的小资产阶级,它摇摆于无产阶级和资产阶级之间,并且作为资产阶级社会的补充部分不断地重新组成。但是,这一阶级的成员经常被竞争抛到无产阶级队伍里去,而且,随着大工业的发展,他们甚至觉察到,他们很快就会完全失去他们作为现代社会中一个独立部分的地位,在商业、工业和农业中很快就会被监工和雇员所代替。 + +在农民阶级远远超过人口半数的国家,例如在法国,那些站在无产阶级方面反对资产阶级的著作家,自然是用小资产阶级和小农的尺度去批判资产阶级制度的,是从小资产阶级的立场出发替工人说话的。这样就形成了小资产阶级的社会主义。西斯蒙第不仅对法国而且对英国来说都是这类著作家的首领。 + +这种社会主义非常透彻地分析了现代生产关系中的矛盾。它揭穿了经济学家的虚伪的粉饰。它确凿地证明了机器和分工的破坏作用、资本和地产的积聚、生产过剩、危机、小资产者和小农的必然没落、无产阶级的贫困、生产的无政府状态、财富分配的极不平均、各民族之间的毁灭性的工业战争,以及旧风尚、旧家庭关系和旧民族性的解体。 + +但是,这种社会主义按其实际内容来说,或者是企图恢复旧的生产资料和交换手段,从而恢复旧的所有制关系和旧的社会,或者是企图重新把现代的生产资料和交换手段硬塞到已被它们突破而且必然被突破的旧的所有制关系的框子里去。它在这两种场合都是反动的,同时又是空想的。 + +工业中的行会制度,农业中的宗法经济,——这就是它的结论。 + +这一思潮在它以后的发展中变成了一种怯懦的悲叹。 + +(丙)德国的或“真正的”社会主义 + +法国的社会主义和共产主义的文献是在居于统治地位的资产阶级的压迫下产生的,并且是同这种统治作斗争的文字表现,这种文献被搬到德国的时候,那里的资产阶级才刚刚开始进行反对封建专制制度的斗争。 + +德国的哲学家、半哲学家和美文学家,贪婪地抓住了这种文献,不过他们忘记了:在这种著作从法国搬到德国的时候,法国的生活条件却没有同时搬过去。在德国的条件下,法国的文献完全失去了直接实践的意义,而只具有纯粹文献的形式。它必然表现为关于真正的社会、关于实现人的本质的无谓思辨。这样,第一次法国革命的要求,在18世纪的德国哲学家看来,不过是一般“实践理性”的要求,而革命的法国资产阶级的意志的表现,在他们心目中就是纯粹的意志、本来的意志、真正人的意志的规律。 + +德国著作家的唯一工作,就是把新的法国的思想同他们的旧的哲学信仰调和起来,或者毋宁说,就是从他们的哲学观点出发去掌握法国的思想。 + +这种掌握,就像掌握外国语一样,是通过翻译的。 + +大家知道,僧侣们曾经在古代异教经典的手抄本上面写上荒诞的天主教圣徒传。德国著作家对世俗的法国文献采取相反的作法。他们在法国的原著下面写上自己的哲学胡说。例如,他们在法国人对货币关系的批判下面写上“人的本质的外化”,在法国人对资产阶级国家的批判下面写上所谓“抽象普遍物的统治的扬弃”,等等。 + +这种在法国人的论述下面塞进自己哲学词句的做法,他们称之为“行动的哲学”、”真正的社会主义”、“德国的社会主义科学”、“社会主义的哲学论证”,等等。 + +法国的社会主义和共产主义的文献就这样被完全阉割了。既然这种文献在德国人手里已不再表现一个阶级反对另一个阶级的斗争,于是德国人就认为:他们克服了“法国人的片面性”,他们不代表真实的要求,而代表真理的要求,不代表无产者的利益,而代表人的本质的利益,即一般人的利益,这种人不属于任何阶级,根本不存在于现实界,而只存在于云雾弥漫的哲学幻想的太空。 + +这种曾经郑重其事地看待自己那一套拙劣的小学生作业并且大言不惭地加以吹嘘的德国社会主义,现在渐渐失去了它的自炫博学的天真。 + +德国的特别是普鲁士的资产阶级反对封建主和专制王朝的斗争,一句话,自由主义运动,越来越严重了。 + +于是,“真正的”社会主义就得到了一个好机会,把社会主义的要求同政治运动对立起来,用诅咒异端邪说的传统办法诅咒自由主义,诅咒代议制国家,诅咒资产阶级的竞争、资产阶级的新闻出版自由、资产阶级的法、资产阶级的自由和平等,并且向人民群众大肆宣扬,说什么在这个资产阶级运动中,人民群众非但一无所得,反而会失去一切。德国的社会主义恰好忘记了,法国的批判(德国的社会主义是这种批判的可怜的回声)是以现代的资产阶级社会以及相应的物质生活条件和相当的政治制度为前提的,而这一切前提当时在德国正是尚待争取的。 + +这种社会主义成了德意志各邦专制政府及其随从——僧侣、教员、容克和官僚求之不得的、吓唬来势汹汹的资产阶级的稻草人。 + +这种社会主义是这些政府用来镇压德国工人起义的毒辣的皮鞭和枪弹的甜蜜的补充。 + +既然“真正的”社会主义就这样成了这些政府对付德国资产阶级的武器,那么它也就直接代表了一种反动的利益,即德国小市民的利益。在德国,16世纪遗留下来的、从那时起经常以不同形式重新出现的小资产阶级,是现存制度的真实的社会基础。 + +保存这个小资产阶级,就是保存德国的现存制度。这个阶级胆战心惊地从资产阶级的工业统治和政治统治那里等候着无可幸免的灭亡,这一方面是由于资本的积聚,另一方面是由于革命无产阶级的兴起。在它看来,“真正的”社会主义能起一箭双雕的作用。“真正的”社会主义像瘟疫一样流行起来了。 + +德国的社会主义者给自己的那几条干瘪的“永恒真理”披上一件用思辨的蛛丝织成的、绣满华丽辞藻的花朵和浸透甜情蜜意的甘露的外衣,这件光彩夺目的外衣只是使他们的货物在这些顾客中间增加销路罢了。 + +同时,德国的社会主义也越来越认识到自己的使命就是充当这种小市民的夸夸其谈的代言人。 + +它宣布德意志民族是模范的民族,德国小市民是模范的人。它给这些小市民的每一种丑行都加上奥秘的、高尚的、社会主义的意义,使之变成完全相反的东西。它发展到最后,就直接反对共产主义的“野蛮破坏的”倾向,并且宣布自己是不偏不倚地超乎任何阶级斗争之上的。现今在德国流行的一切所谓社会主义和共产主义的著作,除了极少数的例外,都属于这一类卑鄙龌龊的、令人委靡的文献。 + +2.保守的或资产阶级的社会主义 + +资产阶级中的一部分人想要消除社会的弊病,以便保障资产阶级社会的生存。 + +这一部分人包括:经济学家、博爱主义者、人道主义者、劳动阶级状况改善派、慈善事业组织者、动物保护协会会员、戒酒协会发起人以及形形色色的小改良家。这种资产阶级的社会主义甚至被制成一些完整的体系。 + +我们可以举蒲鲁东的《贫困的哲学》作为例子。 + +社会主义的资产者愿意要现代社会的生存条件,但是不要由这些条件必然产生的斗争和危险。他们愿意要现存的社会,但是不要那些使这个社会革命化和瓦解的因素。他们愿意要资产阶级,但是不要无产阶级。在资产阶级看来,它所统治的世界自然是最美好的世界。资产阶级的社会主义把这种安慰人心的观念制成半套或整套的体系。它要求无产阶级实现它的体系,走进新的耶路撒冷,其实它不过是要求无产阶级停留在现今的社会里,但是要抛弃他们关于这个社会的可恶的观念。 + +这种社会主义的另一种不够系统、但是比较实际的形式,力图使工人阶级厌弃一切革命运动,硬说能给工人阶级带来好处的并不是这样或那样的政治改革,而仅仅是物质生活条件即经济关系的改变。但是,这种社会主义所理解的物质生活条件的改变,绝对不是只有通过革命的途径才能实现的资产阶级生产关系的废除,而是一些在这种生产关系的基础上实行的行政上的改良,因而丝毫不会改变资本和雇佣劳动的关系,至多只能减少资产阶级的统治费用和简化它的财政管理。 + +资产阶级的社会主义只有在它变成纯粹的演说辞令的时候,才获得自己的适当的表现。 + +自由贸易!为了工人阶级的利益;保护关税!为了工人阶级的利益;单身牢房!为了工人阶级的利益。——这才是资产阶级的社会主义唯一认真说出的最后的话。 + +资产阶级的社会主义就是这样一个论断:资产者之为资产者,是为了工人阶级的利益。 + +3.批判的空想的社会主义和共产主义 + +在这里,我们不谈在现代一切大革命中表达过无产阶级要求的文献(巴贝夫等人的著作)。 + +无产阶级在普遍激动的时代、在推翻封建社会的时期直接实现自己阶级利益的最初尝试,都不可避免地遭到了失败,这是由于当时无产阶级本身还不够发展,由于无产阶级解放的物质条件还没具备,这些条件只是资产阶级时代的产物。随着这些早期的无产阶级运动而出现的革命文献,就其内容来说必然是反动的。这种文献倡导普遍的禁欲主义和粗陋的平均主义。 + +本来意义的社会主义和共产主义的体系,圣西门、傅立叶、欧文等人的体系,是在无产阶级和资产阶级之间的斗争还不发展的最初时期出现的。关于这个时期,我们在前面已经叙述过了(见《资产阶级和无产阶级》)。 + +诚然,这些体系的发明家看到了阶级的对立,以及占统治地位的社会本身中的瓦解因素的作用。但是,他们看不到无产阶级方面的任何历史主动性,看不到它所特有的任何政治运动。 + +由于阶级对立的发展是同工业的发展步调一致的,所以这些发明家也不可能看到无产阶级解放的物质条件,于是他们就去探求某种社会科学、社会规律,以便创造这些条件。 + +社会的活动要由他们个人的发明活动来代替,解放的历史条件要由幻想的条件来代替,无产阶级的逐步组织成为阶级要由一种特意设计出来的社会组织来代替。在他们看来,今后的世界历史不过是宣传和实施他们的社会计划。 + +诚然,他们也意识到,他们的计划主要是代表工人阶级这一受苦最深的阶级的利益。在他们心目中,无产阶级只是一个受苦最深的阶级。 + +但是,由于阶级斗争不发展,由于他们本身的生活状况,他们就以为自己是高高超乎这种阶级对立之上的。他们要改善社会一切成员的生活状况,甚至生活最优裕的成员也包括在内。因此,他们总是不加区别地向整个社会呼吁,而且主要是向统治阶级呼吁。他们以为,人们只要理解他们的体系,就会承认这种体系是最美好的社会的最美好的计划。 + +因此,他们拒绝一切政治行动,特别是一切革命行动;他们想通过和平的途径达到自己的目的,并且企图通过一些小型的、当然不会成功的试验,通过示范的力量来为新的社会福音开辟道路。 + +这种对未来社会的幻想的描绘,在无产阶级还很不发展、因而对本身的地位的认识还基于幻想的时候,是同无产阶级对社会普遍改造的最初的本能的渴望相适应的。 + +但是,这些社会主义和共产主义的著作也含有批判的成分。这些著作抨击现存社会的全部基础。因此,它们提供了启发工人觉悟的极为宝贵的材料。它们关于未来社会的积极的主张,例如消灭城乡对立,消灭家庭,消灭私人营利,消灭雇佣劳动,提倡社会和谐,把国家变成纯粹的生产管理机构,——所有这些主张都只是表明要消灭阶级对立,而这种阶级对立在当时刚刚开始发展,它们所知道的只是这种对立的早期的、不明显的、不确定的形式。因此,这些主张本身还带有纯粹空想的性质。 + +批判的空想的社会主义和共产主义的意义,是同历史的发展成反比的。阶级斗争越发展和越具有确定的形式,这种超乎阶级斗争的幻想,这种反对阶级斗争的幻想,就越失去任何实践意义和任何理论根据。所以,虽然这些体系的创始人在许多方面是革命的,但是他们的信徒总是组成一些反动的宗派。这些信徒无视无产阶级的历史进展,还是死守着老师们的旧观点。因此,他们一贯企图削弱阶级斗争,调和对立。他们还总是梦想用试验的办法来实现自己的社会空想,创办单个的法伦斯泰尔,建立国内移民区,创立小伊加利亚,即袖珍版的新耶路撒冷,——而为了建造这一切空中楼阁,他们就不得不呼吁资产阶级发善心和慷慨解囊。他们逐渐地堕落到上述反动的或保守的社会主义者的一伙中去了,所不同的只是他们更加系统地卖弄学问,狂热地迷信自己那一套社会科学的奇功异效。 + +因此,他们激烈地反对工人的一切政治运动,认为这种运动只是由于盲目地不相信新福音才发生的。 + +在英国,有欧文主义者反对宪章派,在法国,有傅立叶主义者反对改革派。 + +四、共产党人对各种反对党派的态度 + +看过第二章之后,就可以了解共产党人同已经形成的工人政党的关系,因而也就可以了解他们同英国宪章派和北美土地改革派的关系。 + +共产党人为工人阶级的最近的目的和利益而斗争,但是他们在当前的运动中同时代表运动的未来。在法国,共产党人同社会主义民主党联合起来反对保守的和激进的资产阶级,但是并不因此放弃对那些从革命的传统中承袭下来的空谈和幻想采取批判态度的权利。 + +在瑞士,共产党人支持激进派,但是并不忽略这个政党是由互相矛盾的分子组成的,其中一部分是法国式的民主社会主义者,一部分是激进的资产者。 + +在波兰人中间,共产党人支持那个把土地革命当作民族解放的条件的政党,即发动过1846年克拉科夫起义的政党。 + +在德国,只要资产阶级采取革命的行动,共产党就同它一起去反对专制君主制、封建土地所有制和小市民的反动性。 + +但是,共产党一分钟也不忽略教育工人尽可能明确地意识到资产阶级和无产阶级的敌对的对立,以便德国工人能够立刻利用资产阶级统治所必然带来的社会的和政治的条件作为反对资产阶级的武器,以便在推翻德国的反动阶级之后立即开始反对资产阶级本身的斗争。 + +共产党人把自己的主要注意力集中在德国,因为德国正处在资产阶级革命的前夜,因为同17世纪的英国和18世纪的法国相比,德国将在整个欧洲文明更进步的条件下,拥有发展得多的无产阶级去实现这个变革,因而德国的资产阶级革命只能是无产阶级革命的直接序幕。 + +总之,共产党人到处都支持一切反对现存的社会制度和政治制度的革命运动。 + +在所有这些运动中,他们都强调所有制问题是运动的基本问题,不管这个问题的发展程度怎样。 + +最后,共产党人到处都努力争取全世界民主政党之间的团结和协调。 + +共产党人不屑于隐瞒自己的观点和意图。他们公开宣布:他们的目的只有用暴力推翻全部现存的社会制度才能达到。让统治阶级在共产主义革命面前发抖吧。无产者在这个革命中失去的只是锁链。他们获得的将是整个世界。 + +全世界无产者,联合起来! +""") + +query = '福娘的物种' +nodes = rag_worker.retrieve_from_store_with_query(query) +build_prompt = rag_worker.build_prompt(query, nodes) +preview = rag_worker.generate_node_array_preview(nodes) +print(preview) +print(build_prompt) +print(nodes) + +# vs = rag_worker.load_from_checkpoint('./good_man_vector_store') +# rag_worker.add_text_to_vector_store(r"I see that the (0.6.0) index persisted on disk contains: docstore.json, index_store.json and vector_store.json, but they don't seem to contain file paths or title metadata from the original documents, so maybe that's not captured and stored?") +# rag_worker.add_text_to_vector_store(r"Thanks! I'm trying to cluster (all) the vectors, then generate a description (label) for each cluster by sending (just) the vectors in each cluster to GPT to summarize, then associate the vectors with the original documents and classify each document by applying a sort of weighted sum of its cluster-labeled snippets. Not sure how useful that will be, but I want to try! I've got the vectors now (although I'm bit worried that the nested structure I'm getting them from might change without warning in the future!), and I'm able to cluster them, but I don't know how to associate the vectors (via their nodes) back to the original documents yet...") +# res = rag_worker.retrieve_from_store_with_query('cluster') +# rag_worker.save_to_checkpoint(checkpoint_dir = './good_man_vector_store') + +# print(vs) diff --git a/tests/test_key_pattern_manager.py b/tests/test_key_pattern_manager.py index bf84441bd..b555d8b30 100644 --- a/tests/test_key_pattern_manager.py +++ b/tests/test_key_pattern_manager.py @@ -21,10 +21,32 @@ def test_is_openai_api_key_with_valid_key(self): key = "sx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" self.assertFalse(is_openai_api_key(key)) - key = "sess-wg61ZafYHpNz7FFwIH7HGZlbVqUVaeV5tatHCWpl" + key = "sess-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" self.assertTrue(is_openai_api_key(key)) - key = "sess-wg61ZafYHpNz7FFwIH7HGZlbVqUVa5tatHCWpl" + key = "sess-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + self.assertFalse(is_openai_api_key(key)) + + key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx" + self.assertTrue(is_openai_api_key(key)) + key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxx_xxxxxxxxxxxx_xxxxx-xxxxxxxxxxxxxxxxxxxx" + self.assertTrue(is_openai_api_key(key)) + key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxx-xxxxxxxxxxxxxxx_xxxxxxxxxxxx_xxxxx-xxxxxx-xxxxxxxxxxxxx" + self.assertTrue(is_openai_api_key(key)) + key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxx-xxxxxxxxxxxxxxx_xxxxxxxxxxxx_xxxxx-xxxxxxxxxxxxxxxxxx" + self.assertFalse(is_openai_api_key(key)) + key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxx-xxxxxxxxxxxxxxx_xxxxxxxxxxxx_xxxxx-xxxxxxxxxxxxxxxxxxxxx" + self.assertFalse(is_openai_api_key(key)) + + key = "sk-proj-xx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxx-xxxxxxxx" + self.assertTrue(is_openai_api_key(key)) + key = "sk-proj-xx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxx-xxxxxxxx" + self.assertTrue(is_openai_api_key(key)) + key = "sk-proj-xx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxx-xxxxxxxx" + self.assertFalse(is_openai_api_key(key)) + key = "sk-proj-xx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxx" + self.assertFalse(is_openai_api_key(key)) + key = "sk-proj-xx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxx-xxx啊xxxxxxx" self.assertFalse(is_openai_api_key(key)) diff --git a/tests/test_rag.py b/tests/test_rag.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_social_helper.py b/tests/test_social_helper.py new file mode 100644 index 000000000..fd61a5da2 --- /dev/null +++ b/tests/test_social_helper.py @@ -0,0 +1,11 @@ +""" +对项目中的各个插件进行测试。运行方法:直接运行 python tests/test_plugins.py +""" + +import init_test +import os, sys + + +if __name__ == "__main__": + from test_utils import plugin_test + plugin_test(plugin='crazy_functions.Social_Helper->I人助手', main_input="|") diff --git a/themes/common.js b/themes/common.js index 36133d7ac..7f99dffff 100644 --- a/themes/common.js +++ b/themes/common.js @@ -1355,6 +1355,11 @@ async function multiplex_function_begin(multiplex_sel) { call_plugin_via_name(_align_name_in_crazy_function_py); return; } + if (multiplex_sel === "智能召回 RAG") { + let _align_name_in_crazy_function_py = "Rag智能召回"; + call_plugin_via_name(_align_name_in_crazy_function_py); + return; + } } async function run_multiplex_shift(multiplex_sel){ let key = multiplex_sel; diff --git a/toolbox.py b/toolbox.py index 4a1c162e4..900cf234c 100644 --- a/toolbox.py +++ b/toolbox.py @@ -100,16 +100,19 @@ def decorated(request: gradio.Request, cookies:dict, max_length:int, llm_model:s user_name = request.username else: user_name = default_user_name + embed_model = get_conf("EMBEDDING_MODEL") cookies.update({ 'top_p': top_p, 'api_key': cookies['api_key'], 'llm_model': llm_model, + 'embed_model': embed_model, 'temperature': temperature, 'user_name': user_name, }) llm_kwargs = { 'api_key': cookies['api_key'], 'llm_model': llm_model, + 'embed_model': embed_model, 'top_p': top_p, 'max_length': max_length, 'temperature': temperature, @@ -175,7 +178,7 @@ def update_ui(chatbot:ChatBotWithCookies, history, msg="正常", **kwargs): # yield cookies, chatbot_gr, history, msg -def update_ui_lastest_msg(lastmsg:str, chatbot:ChatBotWithCookies, history:list, delay=1): # 刷新界面 +def update_ui_lastest_msg(lastmsg:str, chatbot:ChatBotWithCookies, history:list, delay=1, msg="正常"): # 刷新界面 """ 刷新用户界面 """ @@ -183,7 +186,7 @@ def update_ui_lastest_msg(lastmsg:str, chatbot:ChatBotWithCookies, history:list, chatbot.append(["update_ui_last_msg", lastmsg]) chatbot[-1] = list(chatbot[-1]) chatbot[-1][-1] = lastmsg - yield from update_ui(chatbot=chatbot, history=history) + yield from update_ui(chatbot=chatbot, history=history, msg=msg) time.sleep(delay) @@ -621,9 +624,12 @@ def load_chat_cookies(): } } ) + + EMBEDDING_MODEL = get_conf("EMBEDDING_MODEL") return { "api_key": API_KEY, "llm_model": LLM_MODEL, + "embed_model": EMBEDDING_MODEL, "customize_fn_overwrite": customize_fn_overwrite_, }