mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-07-24 12:45:45 +00:00
多轮对话使用插件
This commit is contained in:
parent
311ae7ada5
commit
1b72dc6605
@ -45,6 +45,7 @@ def execute_ai_response_json(
|
||||
except (json.JSONDecodeError, ValueError, AttributeError) as e:
|
||||
raise NotCommands("非可执行命令结构")
|
||||
command_name, arguments = get_command(assistant_reply_json)
|
||||
|
||||
if cfg.speak_mode:
|
||||
say_text(f"I want to execute {command_name}")
|
||||
|
||||
@ -105,11 +106,16 @@ def execute_command(
|
||||
or command_name == command["name"].lower()
|
||||
):
|
||||
try:
|
||||
|
||||
# 删除非定义参数
|
||||
diff_ags = list(set(arguments.keys()).difference(set(command['args'].keys())))
|
||||
for arg_name in arguments.keys():
|
||||
if arg_name in diff_ags:
|
||||
del arguments[arg_name]
|
||||
print(str(arguments))
|
||||
return command["function"](**arguments)
|
||||
except Exception as e:
|
||||
return f"Error: {str(e)}"
|
||||
raise NotCommands("非可用命令" + command)
|
||||
raise NotCommands("非可用命令" + command_name)
|
||||
|
||||
|
||||
def get_command(response_json: Dict):
|
||||
|
@ -1,3 +1,5 @@
|
||||
|
||||
class NotCommands(Exception):
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
self.message = message
|
@ -105,3 +105,7 @@ class Config(metaclass=Singleton):
|
||||
def set_speak_mode(self, value: bool) -> None:
|
||||
"""Set the speak mode value."""
|
||||
self.speak_mode = value
|
||||
|
||||
def set_last_plugin_return(self, value: bool) -> None:
|
||||
"""Set the speak mode value."""
|
||||
self.last_plugin_return = value
|
@ -28,7 +28,7 @@ VECTOR_SEARCH_TOP_K = 3
|
||||
LLM_MODEL = "vicuna-13b"
|
||||
LIMIT_MODEL_CONCURRENCY = 5
|
||||
MAX_POSITION_EMBEDDINGS = 4096
|
||||
VICUNA_MODEL_SERVER = "http://120.27.148.250:8000"
|
||||
VICUNA_MODEL_SERVER = "http://121.41.167.183:8000"
|
||||
|
||||
# Load model config
|
||||
ISLOAD_8BIT = True
|
||||
|
@ -159,9 +159,12 @@ auto_dbgpt_one_shot = Conversation(
|
||||
1. If you are unsure how you previously did something or want to recall past events, thinking about similar events will help you remember.
|
||||
2. No user assistance
|
||||
3. Exclusively use the commands listed in double quotes e.g. "command name"
|
||||
DBScheme:
|
||||
|
||||
|
||||
|
||||
|
||||
Schema:
|
||||
数据库gpt-user的Schema信息如下: users(city,create_time,email,last_login_time,phone,user_name);
|
||||
|
||||
|
||||
Commands:
|
||||
1. analyze_code: Analyze Code, args: "code": "<full_code_string>"
|
||||
2. execute_python_file: Execute Python File, args: "filename": "<filename>"
|
||||
@ -170,21 +173,9 @@ auto_dbgpt_one_shot = Conversation(
|
||||
5. list_files: List Files in Directory, args: "directory": "<directory>"
|
||||
6. read_file: Read file, args: "filename": "<filename>"
|
||||
7. write_to_file: Write to file, args: "filename": "<filename>", "text": "<text>"
|
||||
8. ob_sql_executor: "Execute SQL in OB Database.", args: "sql": "<sql>"
|
||||
|
||||
Resources:
|
||||
1. Internet access for searches and information gathering.
|
||||
2. Long Term memory management.
|
||||
3. vicuna powered Agents for delegation of simple tasks.
|
||||
|
||||
Performance Evaluation:
|
||||
1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities.
|
||||
2. Constructively self-criticize your big-picture behavior constantly.
|
||||
3. Reflect on past decisions and strategies to refine your approach.
|
||||
4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps.
|
||||
5. Write all code to a file.
|
||||
|
||||
You should only respond in JSON format as described below
|
||||
8. db_sql_executor: "Execute SQL in Database.", args: "sql": "<sql>"
|
||||
|
||||
You should only respond in JSON format as described below and ensure the response can be parsed by Python json.loads
|
||||
Response Format:
|
||||
{
|
||||
"thoughts": {
|
||||
@ -201,7 +192,6 @@ auto_dbgpt_one_shot = Conversation(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ensure the response can be parsed by Python json.loads
|
||||
"""
|
||||
),
|
||||
(
|
||||
@ -209,16 +199,16 @@ auto_dbgpt_one_shot = Conversation(
|
||||
"""
|
||||
{
|
||||
"thoughts": {
|
||||
"text": "To answer how many users by query database we need to write SQL query to get the count of the distinct users from the database. We can use ob_sql_executor command to execute the SQL query in database.",
|
||||
"text": "To answer how many users by query database we need to write SQL query to get the count of the distinct users from the database. We can use db_sql_executor command to execute the SQL query in database.",
|
||||
"reasoning": "We can use the sql_executor command to execute the SQL query for getting count of distinct users from the users database. We can select the count of the distinct users from the users table.",
|
||||
"plan": "- Write SQL query to get count of distinct users from users database\n- Use ob_sql_executor to execute the SQL query in OB database\n- Parse the SQL result to get the count\n- Respond with the count as the answer",
|
||||
"plan": "- Write SQL query to get count of distinct users from users database\n- Use db_sql_executor to execute the SQL query in OB database\n- Parse the SQL result to get the count\n- Respond with the count as the answer",
|
||||
"criticism": "None",
|
||||
"speak": "To get the number of users in users, I will execute an SQL query in OB database using the ob_sql_executor command and respond with the count."
|
||||
"speak": "To get the number of users in users, I will execute an SQL query in OB database using the db_sql_executor command and respond with the count."
|
||||
},
|
||||
"command": {
|
||||
"name": "ob_sql_executor",
|
||||
"name": "db_sql_executor",
|
||||
"args": {
|
||||
"sql": "SELECT COUNT(DISTINCT(*)) FROM users ;"
|
||||
"sql": "SELECT COUNT(DISTINCT(user_name)) FROM users ;"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,10 @@ from pathlib import Path
|
||||
import distro
|
||||
import yaml
|
||||
from pilot.configs.config import Config
|
||||
from pilot.prompts.prompt import build_default_prompt_generator
|
||||
from pilot.prompts.prompt import build_default_prompt_generator, DEFAULT_TRIGGERING_PROMPT
|
||||
|
||||
|
||||
class FirstPrompt:
|
||||
class AutoModePrompt:
|
||||
"""
|
||||
|
||||
"""
|
||||
@ -35,6 +35,51 @@ class FirstPrompt:
|
||||
self.prompt_generator = None
|
||||
self.command_registry = None
|
||||
|
||||
def construct_follow_up_prompt(
|
||||
self,
|
||||
user_input:[str],
|
||||
last_auto_return: str = None,
|
||||
prompt_generator: Optional[PromptGenerator] = None
|
||||
)-> str:
|
||||
"""
|
||||
基于用户输入的后续对话信息构建完整的prompt信息
|
||||
Args:
|
||||
self:
|
||||
prompt_generator:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
prompt_start = (
|
||||
"Based on the above definition, answer the current goal and ensure that the response meets both the current constraints and the above definition and constraints"
|
||||
)
|
||||
if prompt_generator is None:
|
||||
prompt_generator = build_default_prompt_generator()
|
||||
prompt_generator.goals = user_input
|
||||
prompt_generator.command_registry = self.command_registry
|
||||
# 加载插件中可用命令
|
||||
cfg = Config()
|
||||
for plugin in cfg.plugins:
|
||||
if not plugin.can_handle_post_prompt():
|
||||
continue
|
||||
prompt_generator = plugin.post_prompt(prompt_generator)
|
||||
|
||||
|
||||
full_prompt = f"{prompt_start}\n\nNEW GOALS:\n\n"
|
||||
if not self.ai_goals :
|
||||
self.ai_goals = user_input
|
||||
for i, goal in enumerate(self.ai_goals):
|
||||
full_prompt += f"{i+1}.根据提供的Schema信息, {goal}\n"
|
||||
# if last_auto_return == None:
|
||||
# full_prompt += f"{cfg.last_plugin_return}\n\n"
|
||||
# else:
|
||||
# full_prompt += f"{last_auto_return}\n\n"
|
||||
|
||||
full_prompt += f"New Constraints:\n{DEFAULT_TRIGGERING_PROMPT}\n\n"
|
||||
|
||||
full_prompt += "GENERATE NEXT COMMAND JSON, And ensure that JSON is correctly loadable by Python"
|
||||
self.prompt_generator = prompt_generator
|
||||
return full_prompt
|
||||
|
||||
def construct_first_prompt(
|
||||
self,
|
||||
@ -57,10 +102,6 @@ class FirstPrompt:
|
||||
" simple strategies with no legal complications."
|
||||
""
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
if prompt_generator is None:
|
||||
prompt_generator = build_default_prompt_generator()
|
||||
prompt_generator.goals = fisrt_message
|
||||
@ -84,13 +125,12 @@ class FirstPrompt:
|
||||
|
||||
# Construct full prompt
|
||||
full_prompt = f"{prompt_start}\n\nGOALS:\n\n"
|
||||
|
||||
if not self.ai_goals :
|
||||
self.ai_goals = fisrt_message
|
||||
for i, goal in enumerate(self.ai_goals):
|
||||
full_prompt += f"{i+1}. {goal}\n"
|
||||
full_prompt += f"{i+1}.根据提供的Schema信息,{goal}\n"
|
||||
if db_schemes:
|
||||
full_prompt += f"\nDB SCHEME:\n\n"
|
||||
full_prompt += f"\nSchema:\n\n"
|
||||
full_prompt += f"{db_schemes}"
|
||||
|
||||
# if self.api_budget > 0.0:
|
@ -149,7 +149,7 @@ class PromptGenerator:
|
||||
f"Resources:\n{self._generate_numbered_list(self.resources)}\n\n"
|
||||
"Performance Evaluation:\n"
|
||||
f"{self._generate_numbered_list(self.performance_evaluation)}\n\n"
|
||||
"You should only respond in JSON format as described below \nResponse"
|
||||
f" Format: \n{formatted_response_format} \nEnsure the response can be"
|
||||
" parsed by Python json.loads"
|
||||
"You should only respond in JSON format as described below and ensure the"
|
||||
"response can be parsed by Python json.loads \nResponse"
|
||||
f" Format: \n{formatted_response_format}"
|
||||
)
|
||||
|
@ -23,10 +23,10 @@ def build_default_prompt_generator() -> PromptGenerator:
|
||||
prompt_generator = PromptGenerator()
|
||||
|
||||
# Add constraints to the PromptGenerator object
|
||||
prompt_generator.add_constraint(
|
||||
"~4000 word limit for short term memory. Your short term memory is short, so"
|
||||
" immediately save important information to files."
|
||||
)
|
||||
# prompt_generator.add_constraint(
|
||||
# "~4000 word limit for short term memory. Your short term memory is short, so"
|
||||
# " immediately save important information to files."
|
||||
# )
|
||||
prompt_generator.add_constraint(
|
||||
"If you are unsure how you previously did something or want to recall past"
|
||||
" events, thinking about similar events will help you remember."
|
||||
@ -35,31 +35,37 @@ def build_default_prompt_generator() -> PromptGenerator:
|
||||
prompt_generator.add_constraint(
|
||||
'Exclusively use the commands listed in double quotes e.g. "command name"'
|
||||
)
|
||||
prompt_generator.add_constraint(
|
||||
'If there is SQL in the args parameter, ensure to use the database and table definitions in Schema, and ensure that the fields and table names are in the definition'
|
||||
)
|
||||
prompt_generator.add_constraint(
|
||||
'The generated command args need to comply with the definition of the command'
|
||||
)
|
||||
|
||||
# Add resources to the PromptGenerator object
|
||||
prompt_generator.add_resource(
|
||||
"Internet access for searches and information gathering."
|
||||
)
|
||||
prompt_generator.add_resource("Long Term memory management.")
|
||||
prompt_generator.add_resource(
|
||||
"DB-GPT powered Agents for delegation of simple tasks."
|
||||
)
|
||||
# prompt_generator.add_resource(
|
||||
# "Internet access for searches and information gathering."
|
||||
# )
|
||||
# prompt_generator.add_resource("Long Term memory management.")
|
||||
# prompt_generator.add_resource(
|
||||
# "DB-GPT powered Agents for delegation of simple tasks."
|
||||
# )
|
||||
# prompt_generator.add_resource("File output.")
|
||||
|
||||
# Add performance evaluations to the PromptGenerator object
|
||||
prompt_generator.add_performance_evaluation(
|
||||
"Continuously review and analyze your actions to ensure you are performing to"
|
||||
" the best of your abilities."
|
||||
)
|
||||
prompt_generator.add_performance_evaluation(
|
||||
"Constructively self-criticize your big-picture behavior constantly."
|
||||
)
|
||||
prompt_generator.add_performance_evaluation(
|
||||
"Reflect on past decisions and strategies to refine your approach."
|
||||
)
|
||||
prompt_generator.add_performance_evaluation(
|
||||
"Every command has a cost, so be smart and efficient. Aim to complete tasks in"
|
||||
" the least number of steps."
|
||||
)
|
||||
# prompt_generator.add_performance_evaluation(
|
||||
# "Continuously review and analyze your actions to ensure you are performing to"
|
||||
# " the best of your abilities."
|
||||
# )
|
||||
# prompt_generator.add_performance_evaluation(
|
||||
# "Constructively self-criticize your big-picture behavior constantly."
|
||||
# )
|
||||
# prompt_generator.add_performance_evaluation(
|
||||
# "Reflect on past decisions and strategies to refine your approach."
|
||||
# )
|
||||
# prompt_generator.add_performance_evaluation(
|
||||
# "Every command has a cost, so be smart and efficient. Aim to complete tasks in"
|
||||
# " the least number of steps."
|
||||
# )
|
||||
# prompt_generator.add_performance_evaluation("Write all code to a file.")
|
||||
return prompt_generator
|
||||
|
@ -20,7 +20,7 @@ from pilot.configs.model_config import LOGDIR, VICUNA_MODEL_SERVER, LLM_MODEL, D
|
||||
from pilot.plugins import scan_plugins
|
||||
from pilot.configs.config import Config
|
||||
from pilot.commands.command_mange import CommandRegistry
|
||||
from pilot.prompts.first_conversation_prompt import FirstPrompt
|
||||
from pilot.prompts.auto_mode_prompt import AutoModePrompt
|
||||
from pilot.prompts.generator import PromptGenerator
|
||||
|
||||
from pilot.commands.exception_not_commands import NotCommands
|
||||
@ -174,28 +174,25 @@ def http_bot(state, mode, sql_mode, db_selector, temperature, max_new_tokens, re
|
||||
return
|
||||
|
||||
cfg = Config()
|
||||
first_prompt = FirstPrompt()
|
||||
|
||||
auto_prompt = AutoModePrompt()
|
||||
auto_prompt.command_registry = cfg.command_registry
|
||||
|
||||
# TODO when tab mode is AUTO_GPT, Prompt need to rebuild.
|
||||
if len(state.messages) == state.offset + 2:
|
||||
query = state.messages[-2][1]
|
||||
# 第一轮对话需要加入提示Prompt
|
||||
first_prompt.command_registry = cfg.command_registry
|
||||
if sql_mode == conversation_sql_mode["auto_execute_ai_response"]:
|
||||
# autogpt模式的第一轮对话需要 构建专属prompt
|
||||
system_prompt = first_prompt.construct_first_prompt(fisrt_message=[query], db_schemes= gen_sqlgen_conversation(dbname))
|
||||
system_prompt = auto_prompt.construct_first_prompt(fisrt_message=[query], db_schemes= gen_sqlgen_conversation(dbname))
|
||||
logger.info("[TEST]:" + system_prompt)
|
||||
template_name = "auto_dbgpt_one_shot"
|
||||
new_state = conv_templates[template_name].copy()
|
||||
new_state.append_message(role='USER', message=system_prompt)
|
||||
# new_state.append_message(new_state.roles[0], query)
|
||||
new_state.append_message(new_state.roles[1], None)
|
||||
else:
|
||||
template_name = "conv_one_shot"
|
||||
new_state = conv_templates[template_name].copy()
|
||||
|
||||
new_state.conv_id = uuid.uuid4().hex
|
||||
|
||||
if not autogpt:
|
||||
# prompt 中添加上下文提示, 根据已有知识对话, 上下文提示是否也应该放在第一轮, 还是每一轮都添加上下文?
|
||||
# 如果用户侧的问题跨度很大, 应该每一轮都加提示。
|
||||
if db_selector:
|
||||
@ -205,7 +202,20 @@ def http_bot(state, mode, sql_mode, db_selector, temperature, max_new_tokens, re
|
||||
new_state.append_message(new_state.roles[0], query)
|
||||
new_state.append_message(new_state.roles[1], None)
|
||||
|
||||
new_state.conv_id = uuid.uuid4().hex
|
||||
state = new_state
|
||||
else:
|
||||
### 后续对话
|
||||
query = state.messages[-2][1]
|
||||
# 第一轮对话需要加入提示Prompt
|
||||
if sql_mode == conversation_sql_mode["auto_execute_ai_response"]:
|
||||
## 获取最后一次插件的返回
|
||||
follow_up_prompt = auto_prompt.construct_follow_up_prompt([query])
|
||||
state.messages[0][0] = ""
|
||||
state.messages[0][1] = ""
|
||||
state.messages[-2][1] = follow_up_prompt
|
||||
|
||||
|
||||
if mode == conversation_types["default_knownledge"] and not db_selector:
|
||||
query = state.messages[-2][1]
|
||||
knqa = KnownLedgeBaseQA()
|
||||
@ -228,20 +238,50 @@ def http_bot(state, mode, sql_mode, db_selector, temperature, max_new_tokens, re
|
||||
|
||||
if sql_mode == conversation_sql_mode["auto_execute_ai_response"]:
|
||||
response = requests.post(urljoin(VICUNA_MODEL_SERVER, "generate"),
|
||||
headers=headers, json=payload, timeout=30)
|
||||
headers=headers, json=payload, timeout=120)
|
||||
|
||||
print(response.json())
|
||||
print(str(response))
|
||||
try:
|
||||
# response = """{"thoughts":{"text":"thought","reasoning":"reasoning","plan":"- short bulleted\n- list that conveys\n- long-term plan","criticism":"constructive self-criticism","speak":"thoughts summary to say to user"},"command":{"name":"db_sql_executor","args":{"sql":"select count(*) as user_count from users u where create_time >= DATE_SUB(NOW(), INTERVAL 1 MONTH);"}}}"""
|
||||
# response = response.replace("\n", "\\n")
|
||||
plugin_resp = execute_ai_response_json(first_prompt.prompt_generator, response)
|
||||
print(plugin_resp)
|
||||
state.messages[-1][-1] = "DB-GPT执行结果:\n" + plugin_resp
|
||||
yield (state, state.to_gradio_chatbot()) + (no_change_btn,) * 5
|
||||
|
||||
# response = """{"thoughts":{"text":"In order to get the number of users who have grown in the last three days, I need to analyze the create\_time of each user and see if it is within the last three days. I will use the SQL query to filter the users who have created their account in the last three days.","reasoning":"I can use the SQL query to filter the users who have created their account in the last three days. I will get the current date and then subtract three days from it, and then use this as the filter for the query. This will give me the number of users who have created their account in the last three days.","plan":"- Get the current date and subtract three days from it\n- Use the SQL query to filter the users who have created their account in the last three days\n- Count the number of users who match the filter to get the number of users who have grown in the last three days","criticism":"None"},"command":{"name":"db_sql_executor","args":{"sql":"SELECT COUNT(DISTINCT(ID)) FROM users WHERE create_time >= DATE_SUB(NOW(), INTERVAL 3 DAY);"}}}"""
|
||||
# response = response.replace("\n", "\\)
|
||||
text = response.text.strip()
|
||||
text = text.rstrip()
|
||||
respObj = json.loads(text)
|
||||
|
||||
xx = respObj['response']
|
||||
xx = xx.strip(b'\x00'.decode())
|
||||
respObj_ex = json.loads(xx)
|
||||
if respObj_ex['error_code'] == 0:
|
||||
ai_response = None
|
||||
all_text = respObj_ex['text']
|
||||
### 解析返回文本,获取AI回复部分
|
||||
tmpResp = all_text.split(state.sep)
|
||||
last_index = -1
|
||||
for i in range(len(tmpResp)):
|
||||
if tmpResp[i].find('ASSISTANT:') != -1:
|
||||
last_index = i
|
||||
ai_response = tmpResp[last_index]
|
||||
ai_response = ai_response.replace("ASSISTANT:", "")
|
||||
ai_response = ai_response.replace("\n", "")
|
||||
ai_response = ai_response.replace("\_", "_")
|
||||
|
||||
print(ai_response)
|
||||
if ai_response == None:
|
||||
state.messages[-1][-1] = "ASSISTANT未能正确回复,回复结果为:\n" + all_text
|
||||
yield (state, state.to_gradio_chatbot()) + (no_change_btn,) * 5
|
||||
else:
|
||||
plugin_resp = execute_ai_response_json(auto_prompt.prompt_generator, ai_response)
|
||||
cfg.set_last_plugin_return(plugin_resp)
|
||||
print(plugin_resp)
|
||||
state.messages[-1][-1] = "Model推理信息:\n"+ ai_response +"\n\nDB-GPT执行结果:\n" + plugin_resp
|
||||
yield (state, state.to_gradio_chatbot()) + (no_change_btn,) * 5
|
||||
except NotCommands as e:
|
||||
print("命令执行:" + str(e))
|
||||
state.messages[-1][-1] = "命令执行:" + str(e) +"\n模型输出:\n" + str(response)
|
||||
print("命令执行:" + e.message)
|
||||
state.messages[-1][-1] = "命令执行:" + e.message +"\n模型输出:\n" + str(ai_response)
|
||||
yield (state, state.to_gradio_chatbot()) + (no_change_btn,) * 5
|
||||
else:
|
||||
# 流式输出
|
||||
@ -350,8 +390,8 @@ def build_single_model_ui():
|
||||
|
||||
max_output_tokens = gr.Slider(
|
||||
minimum=0,
|
||||
maximum=4096,
|
||||
value=2048,
|
||||
maximum=1024,
|
||||
value=1024,
|
||||
step=64,
|
||||
interactive=True,
|
||||
label="最大输出Token数",
|
||||
@ -359,9 +399,6 @@ def build_single_model_ui():
|
||||
tabs= gr.Tabs()
|
||||
with tabs:
|
||||
tab_sql = gr.TabItem("SQL生成与诊断", elem_id="SQL")
|
||||
sql_mode = gr.Radio(["直接执行结果", "不执行结果"], show_label=False, value="不执行结果")
|
||||
sql_vs_setting = gr.Markdown("自动执行模式下, DB-GPT可以具备执行SQL、从网络读取知识自动化存储学习的能力")
|
||||
sql_mode.change(fn=change_sql_mode, inputs=sql_mode, outputs=sql_vs_setting)
|
||||
with tab_sql:
|
||||
# TODO A selector to choose database
|
||||
with gr.Row(elem_id="db_selector"):
|
||||
@ -370,7 +407,11 @@ def build_single_model_ui():
|
||||
choices=dbs,
|
||||
value=dbs[0] if len(models) > 0 else "",
|
||||
interactive=True,
|
||||
show_label=True).style(container=False)
|
||||
show_label=True).style(container=False)
|
||||
|
||||
sql_mode = gr.Radio(["直接执行结果", "不执行结果"], show_label=False, value="不执行结果")
|
||||
sql_vs_setting = gr.Markdown("自动执行模式下, DB-GPT可以具备执行SQL、从网络读取知识自动化存储学习的能力")
|
||||
sql_mode.change(fn=change_sql_mode, inputs=sql_mode, outputs=sql_vs_setting)
|
||||
tab_auto = gr.TabItem("AUTO-GPT", elem_id="auto")
|
||||
with tab_auto:
|
||||
gr.Markdown("自动执行模式下, DB-GPT可以具备执行SQL、从网络读取知识自动化存储学习的能力")
|
||||
|
Loading…
Reference in New Issue
Block a user