diff --git a/requirements.txt b/requirements.txt index b86f37b0..c4e9bbde 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,8 +2,8 @@ langchain>=0.0.302 fschat[model_worker]==0.2.30 openai sentence_transformers -transformers==4.33.3 -torch>=2.0.1 +transformers>=4.34 +torch>=2.0.1 # 推荐2.1 torchvision torchaudio fastapi>=0.103.2 diff --git a/requirements_api.txt b/requirements_api.txt index 03ed339b..8ecb4927 100644 --- a/requirements_api.txt +++ b/requirements_api.txt @@ -1,8 +1,8 @@ langchain>=0.0.302 -fschat[model_worker]==0.2.30 +fschat[model_worker]>=0.2.30 openai sentence_transformers -transformers>=4.33.3 +transformers>=4.34 torch>=2.0.1 torchvision torchaudio diff --git a/server/agent/callbacks.py b/server/agent/callbacks.py index 3901f7e9..3d143605 100644 --- a/server/agent/callbacks.py +++ b/server/agent/callbacks.py @@ -20,7 +20,7 @@ class Status: agent_action: int = 4 agent_finish: int = 5 error: int = 6 - make_tool: int = 7 + tool_finish: int = 7 class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler): @@ -29,11 +29,19 @@ class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler): self.queue = asyncio.Queue() self.done = asyncio.Event() self.cur_tool = {} - self.out = True async def on_tool_start(self, serialized: Dict[str, Any], input_str: str, *, run_id: UUID, parent_run_id: UUID | None = None, tags: List[str] | None = None, metadata: Dict[str, Any] | None = None, **kwargs: Any) -> None: + + # 对于截断不能自理的大模型,我来帮他截断 + stop_words = ["Observation:", "Thought","\"","(", "\n","\t"] + for stop_word in stop_words: + index = input_str.find(stop_word) + if index != -1: + input_str = input_str[:index] + break + self.cur_tool = { "tool_name": serialized["name"], "input_str": input_str, @@ -44,13 +52,13 @@ class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler): "final_answer": "", "error": "", } + # print("\nInput Str:",self.cur_tool["input_str"]) self.queue.put_nowait(dumps(self.cur_tool)) async def on_tool_end(self, output: str, *, run_id: UUID, parent_run_id: UUID | None = None, tags: List[str] | None = None, **kwargs: Any) -> None: - self.out = True self.cur_tool.update( - status=Status.agent_finish, + status=Status.tool_finish, output_str=output.replace("Answer:", ""), ) self.queue.put_nowait(dumps(self.cur_tool)) @@ -65,19 +73,11 @@ class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler): async def on_llm_new_token(self, token: str, **kwargs: Any) -> None: if token: - if "Action" in token: - self.out = False - self.cur_tool.update( - status=Status.running, - llm_token="\n\n", - ) - self.queue.put_nowait(dumps(self.cur_tool)) - if self.out: - self.cur_tool.update( + self.cur_tool.update( status=Status.running, llm_token=token, - ) - self.queue.put_nowait(dumps(self.cur_tool)) + ) + self.queue.put_nowait(dumps(self.cur_tool)) async def on_llm_start(self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any) -> None: self.cur_tool.update( @@ -87,15 +87,13 @@ class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler): self.queue.put_nowait(dumps(self.cur_tool)) async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None: - self.out = True self.cur_tool.update( status=Status.complete, - llm_token="", + llm_token="\n", ) self.queue.put_nowait(dumps(self.cur_tool)) async def on_llm_error(self, error: Exception | KeyboardInterrupt, **kwargs: Any) -> None: - self.out = True self.cur_tool.update( status=Status.error, error=str(error), @@ -107,4 +105,10 @@ class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler): tags: Optional[List[str]] = None, **kwargs: Any, ) -> None: + # 返回最终答案 + self.cur_tool.update( + status=Status.agent_finish, + final_answer=finish.return_values["output"], + ) + self.queue.put_nowait(dumps(self.cur_tool)) self.cur_tool = {} diff --git a/server/agent/custom_template.py b/server/agent/custom_template.py index aa4aa112..db2d8ad7 100644 --- a/server/agent/custom_template.py +++ b/server/agent/custom_template.py @@ -1,10 +1,12 @@ from __future__ import annotations from langchain.agents import Tool, AgentOutputParser from langchain.prompts import StringPromptTemplate -from typing import List, Union +from typing import List, Union, Tuple, Dict from langchain.schema import AgentAction, AgentFinish import re +from configs.model_config import LLM_MODEL, TEMPERATURE, HISTORY_LEN +begin = False class CustomPromptTemplate(StringPromptTemplate): # The template to use template: str @@ -19,40 +21,78 @@ class CustomPromptTemplate(StringPromptTemplate): for action, observation in intermediate_steps: thoughts += action.log thoughts += f"\nObservation: {observation}\nThought: " - # Set the agent_scratchpad variable to that value + # Set the agent_scratchpad variable to that value kwargs["agent_scratchpad"] = thoughts # Create a tools variable from the list of tools provided kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools]) # Create a list of tool names for the tools provided kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools]) + # Return the formatted templatepr + # print( self.template.format(**kwargs), end="\n\n") return self.template.format(**kwargs) -class CustomOutputParser(AgentOutputParser): - def parse(self, llm_output: str) -> AgentFinish | AgentAction | str: + +class CustomOutputParser(AgentOutputParser): + begin: bool = False + def __init__(self): + super().__init__() + self.begin = True + + def parse(self, llm_output: str) -> AgentFinish | tuple[dict[str, str], str] | AgentAction: # Check if agent should finish + support_agent = ["gpt","Qwen","qwen-api","baichuan-api"] + if not any(agent in LLM_MODEL for agent in support_agent) and self.begin: + self.begin = False + stop_words = ["Observation:"] + min_index = len(llm_output) + for stop_word in stop_words: + index = llm_output.find(stop_word) + if index != -1 and index < min_index: + min_index = index + llm_output = llm_output[:min_index] + if "Final Answer:" in llm_output: + output = llm_output.split("Final Answer:", 1)[-1].strip() + self.begin = True return AgentFinish( # Return values is generally always a dictionary with a single `output` key # It is not recommended to try anything else at the moment :) - return_values={"output": llm_output.replace("Final Answer:", "").strip()}, + # return_values={"output": llm_output.replace("Final Answer:", "").strip()}, + return_values={"output": output}, log=llm_output, ) + # Parse out the action and action input - regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)" - match = re.search(regex, llm_output, re.DOTALL) - if not match: + parts = llm_output.split("Action:") + if len(parts) < 2: return AgentFinish( return_values={"output": f"调用agent失败: `{llm_output}`"}, log=llm_output, ) - action = match.group(1).strip() - action_input = match.group(2) + + action = parts[1].split("Action Input:")[0].strip() + action_input = parts[1].split("Action Input:")[1].strip() + + # 原来的正则化检查方式 + # regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)" + # print("llm_output",llm_output) + # match = re.search(regex, llm_output, re.DOTALL) + # print("match",match) + # if not match: + # return AgentFinish( + # return_values={"output": f"调用agent失败: `{llm_output}`"}, + # log=llm_output, + # ) + # action = match.group(1).strip() + # action_input = match.group(2) + # Return the action and action input + try: ans = AgentAction( - tool=action, - tool_input=action_input.strip(" ").strip('"'), - log=llm_output + tool=action, + tool_input=action_input.strip(" ").strip('"'), + log=llm_output ) return ans except: @@ -60,6 +100,3 @@ class CustomOutputParser(AgentOutputParser): return_values={"output": f"调用agent失败: `{llm_output}`"}, log=llm_output, ) - - - diff --git a/server/agent/math.py b/server/agent/math.py index a00667af..b4056d07 100644 --- a/server/agent/math.py +++ b/server/agent/math.py @@ -1,11 +1,14 @@ +## 单独运行的时候需要添加 +import sys +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + from langchain.prompts import PromptTemplate from langchain.chains import LLMMathChain -from server.utils import wrap_done, get_ChatOpenAI +from server.utils import get_ChatOpenAI from configs.model_config import LLM_MODEL, TEMPERATURE -from langchain.chat_models import ChatOpenAI -from langchain.callbacks.manager import CallbackManagerForToolRun - -_PROMPT_TEMPLATE = """将数学问题翻译成可以使用Python的numexpr库执行的表达式。使用运行此代码的输出来回答问题。 +_PROMPT_TEMPLATE = """ +将数学问题翻译成可以使用Python的numexpr库执行的表达式。使用运行此代码的输出来回答问题。 问题: ${{包含数学问题的问题。}} ```text ${{解决问题的单行数学表达式}} @@ -68,3 +71,8 @@ def calculate(query: str): llm_math = LLMMathChain.from_llm(model, verbose=True, prompt=PROMPT) ans = llm_math.run(query) return ans + +if __name__ == "__main__": + result = calculate("2的三次方") + print("答案:",result) + diff --git a/server/agent/tools.py b/server/agent/tools.py index 2d86f288..0f94529f 100644 --- a/server/agent/tools.py +++ b/server/agent/tools.py @@ -20,12 +20,12 @@ tools = [ Tool.from_function( func=translate, name="翻译工具", - description="翻译各种语言" + description="如果你无法访问互联网,并且需要翻译各种语言,应该使用这个工具" ), Tool.from_function( func=weathercheck, name="天气查询工具", - description="查询天气", + description="如果你无法访问互联网,并需要查询中国各地未来24小时的天气,你应该使用这个工具,每轮对话仅能使用一次", ), Tool.from_function( func=shell, @@ -35,12 +35,12 @@ tools = [ Tool.from_function( func=search_knowledge, name="知识库查询工具", - description="使用西交利物浦大学大数据专业的本专业数据库来解答问题", + description="访问知识库来获取答案", ), Tool.from_function( func=search_internet, name="互联网查询工具", - description="访问Bing互联网来解答问题", + description="如果你无法访问互联网,这个工具可以帮助你访问Bing互联网来解答问题", ), ] diff --git a/server/agent/translator.py b/server/agent/translator.py index d92dd1fc..96b4c8f8 100644 --- a/server/agent/translator.py +++ b/server/agent/translator.py @@ -1,11 +1,10 @@ -from langchain.prompts import PromptTemplate -from langchain.chains import LLMChain +## 单独运行的时候需要添加 import sys import os - -from server.utils import get_ChatOpenAI - sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) +from langchain.prompts import PromptTemplate +from langchain.chains import LLMChain +from server.utils import get_ChatOpenAI from langchain.chains.llm_math.prompt import PROMPT from configs.model_config import LLM_MODEL,TEMPERATURE @@ -16,25 +15,12 @@ _PROMPT_TEMPLATE = ''' 2. 无论提供的是陈述句或疑问句,只进行翻译 3. 不添加与原文无关的内容 -原文: ${{用户需要翻译的原文和目标语言}} -{question} -```output -${{翻译结果}} -``` -答案: ${{答案}} +问题: ${{用户需要翻译的原文和目标语言}} +答案: 你翻译结果 + +现在,这是我的问题: +问题: {question} -以下是两个例子 -问题: 翻译13成英语 -```text -13 英语 -```output -thirteen -以下是两个例子 -问题: 翻译 我爱你 成法语 -```text -13 法语 -```output -Je t'aime. ''' PROMPT = PromptTemplate( @@ -51,5 +37,8 @@ def translate(query: str): ) llm_translate = LLMChain(llm=model, prompt=PROMPT) ans = llm_translate.run(query) + return ans - return ans \ No newline at end of file +if __name__ == "__main__": + result = translate("Can Love remember the question and the answer? 这句话如何诗意的翻译成中文") + print("答案:",result) \ No newline at end of file diff --git a/server/agent/weather.py b/server/agent/weather.py index 3e5a37bf..54f196cf 100644 --- a/server/agent/weather.py +++ b/server/agent/weather.py @@ -1,10 +1,12 @@ -## 使用和风天气API查询天气 +## 使用和风天气API查询天气,这个模型仅仅对免费的API进行了适配 +## 这个模型的提示词非常复杂,我们推荐使用GPT4模型进行运行 + from __future__ import annotations ## 单独运行的时候需要添加 import sys import os -# sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) from server.utils import get_ChatOpenAI @@ -26,9 +28,71 @@ from langchain.schema.language_model import BaseLanguageModel import requests from typing import List, Any, Optional from configs.model_config import LLM_MODEL, TEMPERATURE +from datetime import datetime +from langchain.prompts import PromptTemplate + ## 使用和风天气API查询天气 -KEY = "" +KEY = "ac880e5a877042809ac7ffdd19d95b0d" + + + +_PROMPT_TEMPLATE = """ +用户会提出一个关于天气的问题,你的目标是拆分出用户问题中的区,市 并按照我提供的工具回答。 +例如 用户提出的问题是: 上海浦东未来1小时天气情况? +则 提取的市和区是: 上海 浦东 +如果用户提出的问题是: 上海未来1小时天气情况? +则 提取的市和区是: 上海 None +请注意以下内容: +1. 如果你没有找到区的内容,则一定要使用 None 替代,否则程序无法运行 +2. 如果用户没有指定市 则直接返回缺少信息 + +问题: ${{用户的问题}} + +你的回答格式应该按照下面的内容,请注意,格式内的```text 等标记都必须输出,这是我用来提取答案的标记。 +```text + +${{拆分的市和区,中间用空格隔开}} +``` +... weathercheck(市 区)... +```output + +${{提取后的答案}} +``` +答案: ${{答案}} + + + +这是一个例子: +问题: 上海浦东未来1小时天气情况? + + +```text +上海 浦东 +``` +...weathercheck(上海 浦东)... + +```output +预报时间: 1小时后 +具体时间: 今天 18:00 +温度: 24°C +天气: 多云 +风向: 西南风 +风速: 7级 +湿度: 88% +降水概率: 16% + +Answer: 上海浦东一小时后的天气是多云。 + +现在,这是我的问题: + +问题: {question} +""" +PROMPT = PromptTemplate( + input_variables=["question"], + template=_PROMPT_TEMPLATE, +) + def get_city_info(location, adm, key): base_url = 'https://geoapi.qweather.com/v2/city/lookup?' @@ -38,12 +102,9 @@ def get_city_info(location, adm, key): return data -from datetime import datetime - - -def format_weather_data(data): +def format_weather_data(data,place): hourly_forecast = data['hourly'] - formatted_data = '' + formatted_data = f"\n 这是查询到的关于{place}未来24小时的天气信息: \n" for forecast in hourly_forecast: # 将预报时间转换为datetime对象 forecast_time = datetime.strptime(forecast['fxTime'], '%Y-%m-%dT%H:%M%z') @@ -71,12 +132,11 @@ def format_weather_data(data): elif hours_diff >= 24: # 如果超过24小时,转换为天数 days_diff = hours_diff // 24 - hours_diff_str = str(int(days_diff)) + '天后' + hours_diff_str = str(int(days_diff)) + '天' else: - hours_diff_str = str(int(hours_diff)) + '小时后' + hours_diff_str = str(int(hours_diff)) + '小时' # 将预报时间和当前时间的差值添加到输出中 - formatted_data += '预报时间: ' + hours_diff_str + '\n' - formatted_data += '具体时间: ' + forecast_time_str + '\n' + formatted_data += '预报时间: ' + forecast_time_str + ' 距离现在有: ' + hours_diff_str + '\n' formatted_data += '温度: ' + forecast['temp'] + '°C\n' formatted_data += '天气: ' + forecast['text'] + '\n' formatted_data += '风向: ' + forecast['windDir'] + '\n' @@ -84,53 +144,54 @@ def format_weather_data(data): formatted_data += '湿度: ' + forecast['humidity'] + '%\n' formatted_data += '降水概率: ' + forecast['pop'] + '%\n' # formatted_data += '降水量: ' + forecast['precip'] + 'mm\n' - formatted_data += '\n\n' + formatted_data += '\n' return formatted_data -def get_weather(key, location_id, time: str = "24"): - if time: - url = "https://devapi.qweather.com/v7/weather/" + time + "h?" - else: - time = "3" # 免费订阅只能查看3天的天气 - url = "https://devapi.qweather.com/v7/weather/" + time + "d?" +def get_weather(key, location_id,place): + url = "https://devapi.qweather.com/v7/weather/24h?" params = { 'location': location_id, 'key': key, } response = requests.get(url, params=params) data = response.json() - return format_weather_data(data) + return format_weather_data(data,place) def split_query(query): parts = query.split() - location = parts[0] if parts[0] != 'None' else parts[1] - adm = parts[1] - time = parts[2] - return location, adm, time + adm = parts[0] + location = parts[1] if parts[1] != 'None' else adm + return location, adm def weather(query): - location, adm, time = split_query(query) + location, adm= split_query(query) key = KEY - if time != "None" and int(time) > 24: - return "只能查看24小时内的天气,无法回答" - if time == "None": - time = "24" # 免费的版本只能24小时内的天气 if key == "": return "请先在代码中填入和风天气API Key" - city_info = get_city_info(location=location, adm=adm, key=key) - location_id = city_info['location'][0]['id'] - weather_data = get_weather(key=key, location_id=location_id, time=time) - return weather_data - + try: + city_info = get_city_info(location=location, adm=adm, key=key) + location_id = city_info['location'][0]['id'] + place = adm + "市" + location + "区" + weather_data = get_weather(key=key, location_id=location_id,place=place) + return weather_data + "以上是查询到的天气信息,请你查收\n" + except KeyError: + try: + city_info = get_city_info(location=adm, adm=adm, key=key) + location_id = city_info['location'][0]['id'] + place = adm + "市" + weather_data = get_weather(key=key, location_id=location_id,place=place) + return weather_data + "重要提醒:用户提供的市和区中,区的信息不存在,或者出现错别字,因此该信息是关于市的天气,请你查收\n" + except KeyError: + return "输入的地区不存在,无法提供天气预报" class LLMWeatherChain(Chain): llm_chain: LLMChain llm: Optional[BaseLanguageModel] = None """[Deprecated] LLM wrapper to use.""" - prompt: BasePromptTemplate + prompt: BasePromptTemplate = PROMPT """[Deprecated] Prompt to use to translate to python if necessary.""" input_key: str = "question" #: :meta private: output_key: str = "answer" #: :meta private: @@ -175,7 +236,8 @@ class LLMWeatherChain(Chain): output = weather(expression) except Exception as e: output = "输入的信息有误,请再次尝试" - # raise ValueError(f"错误: {expression},输入的信息不对") + return {self.output_key: output} + raise ValueError(f"错误: {expression},输入的信息不对") return output @@ -198,7 +260,8 @@ class LLMWeatherChain(Chain): elif "Answer:" in llm_output: answer = "Answer: " + llm_output.split("Answer:")[-1] else: - raise ValueError(f"unknown format from LLM: {llm_output}") + return {self.output_key: f"输入的格式不对: {llm_output},应该输入 (市 区)的组合"} + # raise ValueError(f"unknown format from LLM: {llm_output}") return {self.output_key: answer} async def _aprocess_llm_result( @@ -259,92 +322,13 @@ class LLMWeatherChain(Chain): def from_llm( cls, llm: BaseLanguageModel, - prompt: BasePromptTemplate, + prompt: BasePromptTemplate = PROMPT, **kwargs: Any, ) -> LLMWeatherChain: llm_chain = LLMChain(llm=llm, prompt=prompt) return cls(llm_chain=llm_chain, **kwargs) -from langchain.prompts import PromptTemplate - -_PROMPT_TEMPLATE = """用户将会向您咨询天气问题,您不需要自己回答天气问题,而是将用户提问的信息提取出来区,市和时间三个元素后使用我为你编写好的工具进行查询并返回结果,格式为 区+市+时间 每个元素用空格隔开。如果缺少信息,则用 None 代替。 -问题: ${{用户的问题}} - -```text - -${{拆分的区,市和时间}} -``` - -... weather(提取后的关键字,用空格隔开)... -```output - -${{提取后的答案}} -``` -答案: ${{答案}} - -这是两个例子: -问题: 上海浦东未来1小时天气情况? - -```text -浦东 上海 1 -``` -...weather(浦东 上海 1)... - -```output - -预报时间: 1小时后 -具体时间: 今天 18:00 -温度: 24°C -天气: 多云 -风向: 西南风 -风速: 7级 -湿度: 88% -降水概率: 16% - -Answer: -预报时间: 1小时后 -具体时间: 今天 18:00 -温度: 24°C -天气: 多云 -风向: 西南风 -风速: 7级 -湿度: 88% -降水概率: 16% - -问题: 北京市朝阳区未来24小时天气如何? -```text - -朝阳 北京 24 -``` -...weather(朝阳 北京 24)... -```output -预报时间: 23小时后 -具体时间: 明天 17:00 -温度: 26°C -天气: 霾 -风向: 西南风 -风速: 11级 -湿度: 65% -降水概率: 20% -Answer: -预报时间: 23小时后 -具体时间: 明天 17:00 -温度: 26°C -天气: 霾 -风向: 西南风 -风速: 11级 -湿度: 65% -降水概率: 20% - -现在,这是我的问题: -问题: {question} -""" -PROMPT = PromptTemplate( - input_variables=["question"], - template=_PROMPT_TEMPLATE, -) - def weathercheck(query: str): model = get_ChatOpenAI( @@ -357,9 +341,4 @@ def weathercheck(query: str): return ans if __name__ == '__main__': - - ## 检测api是否能正确返回 - query = "上海浦东未来1小时天气情况" - # ans = weathercheck(query) - ans = weather("浦东 上海 1") - print(ans) \ No newline at end of file + result = weathercheck("苏州工姑苏区今晚热不热?") \ No newline at end of file diff --git a/server/chat/agent_chat.py b/server/chat/agent_chat.py index c08ae81c..df0ce8b4 100644 --- a/server/chat/agent_chat.py +++ b/server/chat/agent_chat.py @@ -53,13 +53,11 @@ async def agent_chat(query: str = Body(..., description="用户输入", examples agent = LLMSingleActionAgent( llm_chain=llm_chain, output_parser=output_parser, - stop=["Observation:", "Observation:\n", "<|im_end|>"], # Qwen模型中使用这个 - # stop=["Observation:", "Observation:\n"], # 其他模型,注意模板 + stop=["\nObservation:", "Observation:", "<|im_end|>"], # Qwen模型中使用这个 allowed_tools=tool_names, ) # 把history转成agent的memory memory = ConversationBufferWindowMemory(k=HISTORY_LEN * 2) - for message in history: # 检查消息的角色 if message.role == 'user': @@ -74,29 +72,41 @@ async def agent_chat(query: str = Body(..., description="用户输入", examples memory=memory, ) input_msg = History(role="user", content="{{ input }}").to_msg_template(False) - task = asyncio.create_task(wrap_done( - agent_executor.acall(query, callbacks=[callback], include_run_info=True), - callback.done), - ) + while True: + try: + task = asyncio.create_task(wrap_done( + agent_executor.acall(query, callbacks=[callback], include_run_info=True), + callback.done)) + break + except: + pass if stream: async for chunk in callback.aiter(): tools_use = [] # Use server-sent-events to stream the response data = json.loads(chunk) - if data["status"] == Status.error: - tools_use.append("工具调用失败:\n" + data["error"]) - yield json.dumps({"tools": tools_use}, ensure_ascii=False) - yield json.dumps({"answer": "(工具调用失败,请查看工具栏报错) \n\n"}, ensure_ascii=False) if data["status"] == Status.start or data["status"] == Status.complete: continue - if data["status"] == Status.agent_action: - yield json.dumps({"answer": "(正在使用工具,请注意工具栏变化) \n\n"}, ensure_ascii=False) - if data["status"] == Status.agent_finish: + if data["status"] == Status.error: tools_use.append("工具名称: " + data["tool_name"]) + tools_use.append("工具状态: " + "调用失败") + tools_use.append("错误信息: " + data["error"]) + tools_use.append("重新开始尝试") + tools_use.append("\n```\n") + yield json.dumps({"tools": tools_use}, ensure_ascii=False) + if data["status"] == Status.agent_action: + yield json.dumps({"answer": "\n\n```\n\n"}, ensure_ascii=False) + if data["status"] == Status.tool_finish: + tools_use.append("工具名称: " + data["tool_name"]) + tools_use.append("工具状态: " + "调用成功") tools_use.append("工具输入: " + data["input_str"]) tools_use.append("工具输出: " + data["output_str"]) + tools_use.append("\n```\n") yield json.dumps({"tools": tools_use}, ensure_ascii=False) - yield json.dumps({"answer": data["llm_token"]}, ensure_ascii=False) + if data["status"] == Status.agent_finish: + yield json.dumps({"final_answer": data["final_answer"]}, ensure_ascii=False) + else: + yield json.dumps({"answer": data["llm_token"]}, ensure_ascii=False) else: pass diff --git a/server/model_workers/base.py b/server/model_workers/base.py index 515c5db9..ea141046 100644 --- a/server/model_workers/base.py +++ b/server/model_workers/base.py @@ -44,7 +44,8 @@ class ApiModelWorker(BaseModelWorker): def count_token(self, params): # TODO:需要完善 - print("count token") + # print("count token") + print("\n\n\n") print(params) prompt = params["prompt"] return {"count": len(str(prompt)), "error_code": 0} diff --git a/server/model_workers/zhipu.py b/server/model_workers/zhipu.py index 18cec5b5..321f01f6 100644 --- a/server/model_workers/zhipu.py +++ b/server/model_workers/zhipu.py @@ -26,10 +26,10 @@ class ChatGLMWorker(ApiModelWorker): # 这里的是chatglm api的模板,其它API的conv_template需要定制 self.conv = conv.Conversation( name=self.model_names[0], - system_message="你是一个聪明、对人类有帮助的人工智能,你可以对人类提出的问题给出有用、详细、礼貌的回答。", + system_message="你是一个聪明的助手,请根据用户的提示来完成任务", messages=[], roles=["Human", "Assistant"], - sep="\n### ", + sep="\n###", stop_str="###", ) @@ -57,7 +57,7 @@ class ChatGLMWorker(ApiModelWorker): def get_embeddings(self, params): # TODO: 支持embeddings print("embedding") - print(params) + # print(params) if __name__ == "__main__": diff --git a/startup.py b/startup.py index 419c1598..24099bef 100644 --- a/startup.py +++ b/startup.py @@ -158,7 +158,7 @@ def create_model_worker_app(log_level: str = "INFO", **kwargs) -> FastAPI: else: from fastchat.serve.model_worker import app, GptqConfig, AWQConfig, ModelWorker args.gpus = "0" # GPU的编号,如果有多个GPU,可以设置为"0,1,2,3" - args.max_gpu_memory = "20GiB" + args.max_gpu_memory = "22GiB" args.num_gpus = 1 # model worker的切分是model并行,这里填写显卡的数量 args.load_8bit = False @@ -170,7 +170,7 @@ def create_model_worker_app(log_level: str = "INFO", **kwargs) -> FastAPI: args.awq_ckpt = None args.awq_wbits = 16 args.awq_groupsize = -1 - args.model_names = [] + args.model_names = [""] args.conv_template = None args.limit_worker_concurrency = 5 args.stream_interval = 2 diff --git a/webui_pages/dialogue/dialogue.py b/webui_pages/dialogue/dialogue.py index b6306946..7af2dc55 100644 --- a/webui_pages/dialogue/dialogue.py +++ b/webui_pages/dialogue/dialogue.py @@ -7,7 +7,6 @@ import os from configs import LLM_MODEL, TEMPERATURE from server.utils import get_model_worker_config from typing import List, Dict - chat_box = ChatBox( assistant_avatar=os.path.join( "img", @@ -16,6 +15,9 @@ chat_box = ChatBox( ) + + + def get_messages_history(history_len: int, content_in_expander: bool = False) -> List[Dict]: ''' 返回消息历史。 @@ -104,6 +106,8 @@ def dialogue_page(api: ApiRequest): temperature = st.slider("Temperature:", 0.0, 1.0, TEMPERATURE, 0.05) history_len = st.number_input("历史对话轮数:", 0, 20, HISTORY_LEN) + LLM_MODEL_WEBUI = llm_model + TEMPERATURE_WEBUI = temperature def on_kb_change(): st.toast(f"已加载知识库: {st.session_state.selected_kb}") @@ -155,9 +159,17 @@ def dialogue_page(api: ApiRequest): elif dialogue_mode == "自定义Agent问答": chat_box.ai_say([ - f"正在思考和寻找工具 ...",]) + f"正在思考...", + Markdown("...", in_expander=True, title="思考过程", state="complete"), + ]) text = "" - element_index = 0 + ans = "" + support_agent = ["gpt", "Qwen", "qwen-api", "baichuan-api"] # 目前支持agent的模型 + if not any(agent in llm_model for agent in support_agent): + ans += "正在思考... \n\n 改模型并没有进行Agent对齐,无法正常使用Agent功能!\n\n\n请更换 GPT4或Qwen-14B等支持Agent的模型获得更好的体验! \n\n\n" + chat_box.update_msg(ans, element_index=0, streaming=False) + + for d in api.agent_chat(prompt, history=history, model=llm_model, @@ -169,14 +181,17 @@ def dialogue_page(api: ApiRequest): if error_msg := check_error_msg(d): # check whether error occured st.error(error_msg) + elif chunk := d.get("final_answer"): + ans += chunk + chat_box.update_msg(ans, element_index=0) elif chunk := d.get("answer"): text += chunk - chat_box.update_msg(text, element_index=0) + chat_box.update_msg(text, element_index=1) elif chunk := d.get("tools"): - element_index += 1 - chat_box.insert_msg(Markdown("...", in_expander=True, title="使用工具...", state="complete")) - chat_box.update_msg("\n\n".join(d.get("tools", [])), element_index=element_index, streaming=False) - chat_box.update_msg(text, element_index=0, streaming=False) + text += "\n\n".join(d.get("tools", [])) + chat_box.update_msg(text, element_index=1) + chat_box.update_msg(ans, element_index=0, streaming=False) + chat_box.update_msg(text, element_index=1, streaming=False) elif dialogue_mode == "知识库问答": chat_box.ai_say([ f"正在查询知识库 `{selected_kb}` ...", diff --git a/webui_pages/utils.py b/webui_pages/utils.py index a2113685..4a26f200 100644 --- a/webui_pages/utils.py +++ b/webui_pages/utils.py @@ -250,7 +250,7 @@ class ApiRequest: logger.error(f'{e.__class__.__name__}: {msg}', exc_info=e if log_verbose else None) else: - print(chunk, end="", flush=True) + # print(chunk, end="", flush=True) yield chunk except httpx.ConnectError as e: msg = f"无法连接API服务器,请确认 ‘api.py’ 已正常启动。({e})"