Agent大更新合并 (#1666)

* 更新上agent提示词代码

* 更新部分文档,修复了issue中提到的bge匹配超过1 的bug

* 按需修改

* 解决了部分最新用户用依赖的bug,加了两个工具,移除google工具

* Agent大幅度优化

1. 修改了UI界面
(1)高亮所有没有进行agent对齐的模型,
(2)优化输出体验和逻辑,使用markdown

2. 降低天气工具使用门槛
3. 依赖更新
(1) vllm 更新到0.2.0,增加了一些参数
(2) torch 建议更新到2.1
(3)pydantic不要更新到1.10.12
This commit is contained in:
zR 2023-10-07 11:26:11 +08:00 committed by GitHub
parent 387b4cb967
commit 2c8fc95f7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 267 additions and 224 deletions

View File

@ -2,8 +2,8 @@ langchain>=0.0.302
fschat[model_worker]==0.2.30 fschat[model_worker]==0.2.30
openai openai
sentence_transformers sentence_transformers
transformers==4.33.3 transformers>=4.34
torch>=2.0.1 torch>=2.0.1 # 推荐2.1
torchvision torchvision
torchaudio torchaudio
fastapi>=0.103.2 fastapi>=0.103.2

View File

@ -1,8 +1,8 @@
langchain>=0.0.302 langchain>=0.0.302
fschat[model_worker]==0.2.30 fschat[model_worker]>=0.2.30
openai openai
sentence_transformers sentence_transformers
transformers>=4.33.3 transformers>=4.34
torch>=2.0.1 torch>=2.0.1
torchvision torchvision
torchaudio torchaudio

View File

@ -20,7 +20,7 @@ class Status:
agent_action: int = 4 agent_action: int = 4
agent_finish: int = 5 agent_finish: int = 5
error: int = 6 error: int = 6
make_tool: int = 7 tool_finish: int = 7
class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler): class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler):
@ -29,11 +29,19 @@ class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler):
self.queue = asyncio.Queue() self.queue = asyncio.Queue()
self.done = asyncio.Event() self.done = asyncio.Event()
self.cur_tool = {} self.cur_tool = {}
self.out = True
async def on_tool_start(self, serialized: Dict[str, Any], input_str: str, *, run_id: UUID, 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, parent_run_id: UUID | None = None, tags: List[str] | None = None,
metadata: Dict[str, Any] | None = None, **kwargs: Any) -> 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 = { self.cur_tool = {
"tool_name": serialized["name"], "tool_name": serialized["name"],
"input_str": input_str, "input_str": input_str,
@ -44,13 +52,13 @@ class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler):
"final_answer": "", "final_answer": "",
"error": "", "error": "",
} }
# print("\nInput Str:",self.cur_tool["input_str"])
self.queue.put_nowait(dumps(self.cur_tool)) 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, 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: tags: List[str] | None = None, **kwargs: Any) -> None:
self.out = True
self.cur_tool.update( self.cur_tool.update(
status=Status.agent_finish, status=Status.tool_finish,
output_str=output.replace("Answer:", ""), output_str=output.replace("Answer:", ""),
) )
self.queue.put_nowait(dumps(self.cur_tool)) 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: async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
if token: if token:
if "Action" in token: self.cur_tool.update(
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(
status=Status.running, status=Status.running,
llm_token=token, 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: async def on_llm_start(self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any) -> None:
self.cur_tool.update( self.cur_tool.update(
@ -87,15 +87,13 @@ class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler):
self.queue.put_nowait(dumps(self.cur_tool)) self.queue.put_nowait(dumps(self.cur_tool))
async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None: async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
self.out = True
self.cur_tool.update( self.cur_tool.update(
status=Status.complete, status=Status.complete,
llm_token="", llm_token="\n",
) )
self.queue.put_nowait(dumps(self.cur_tool)) self.queue.put_nowait(dumps(self.cur_tool))
async def on_llm_error(self, error: Exception | KeyboardInterrupt, **kwargs: Any) -> None: async def on_llm_error(self, error: Exception | KeyboardInterrupt, **kwargs: Any) -> None:
self.out = True
self.cur_tool.update( self.cur_tool.update(
status=Status.error, status=Status.error,
error=str(error), error=str(error),
@ -107,4 +105,10 @@ class CustomAsyncIteratorCallbackHandler(AsyncIteratorCallbackHandler):
tags: Optional[List[str]] = None, tags: Optional[List[str]] = None,
**kwargs: Any, **kwargs: Any,
) -> None: ) -> 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 = {} self.cur_tool = {}

View File

@ -1,10 +1,12 @@
from __future__ import annotations from __future__ import annotations
from langchain.agents import Tool, AgentOutputParser from langchain.agents import Tool, AgentOutputParser
from langchain.prompts import StringPromptTemplate from langchain.prompts import StringPromptTemplate
from typing import List, Union from typing import List, Union, Tuple, Dict
from langchain.schema import AgentAction, AgentFinish from langchain.schema import AgentAction, AgentFinish
import re import re
from configs.model_config import LLM_MODEL, TEMPERATURE, HISTORY_LEN
begin = False
class CustomPromptTemplate(StringPromptTemplate): class CustomPromptTemplate(StringPromptTemplate):
# The template to use # The template to use
template: str template: str
@ -19,40 +21,78 @@ class CustomPromptTemplate(StringPromptTemplate):
for action, observation in intermediate_steps: for action, observation in intermediate_steps:
thoughts += action.log thoughts += action.log
thoughts += f"\nObservation: {observation}\nThought: " 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 kwargs["agent_scratchpad"] = thoughts
# Create a tools variable from the list of tools provided # Create a tools variable from the list of tools provided
kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools]) kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools])
# Create a list of tool names for the tools provided # Create a list of tool names for the tools provided
kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools]) 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) 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 # 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: if "Final Answer:" in llm_output:
output = llm_output.split("Final Answer:", 1)[-1].strip()
self.begin = True
return AgentFinish( return AgentFinish(
# Return values is generally always a dictionary with a single `output` key # Return values is generally always a dictionary with a single `output` key
# It is not recommended to try anything else at the moment :) # 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, log=llm_output,
) )
# Parse out the action and action input # Parse out the action and action input
regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)" parts = llm_output.split("Action:")
match = re.search(regex, llm_output, re.DOTALL) if len(parts) < 2:
if not match:
return AgentFinish( return AgentFinish(
return_values={"output": f"调用agent失败: `{llm_output}`"}, return_values={"output": f"调用agent失败: `{llm_output}`"},
log=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 # Return the action and action input
try: try:
ans = AgentAction( ans = AgentAction(
tool=action, tool=action,
tool_input=action_input.strip(" ").strip('"'), tool_input=action_input.strip(" ").strip('"'),
log=llm_output log=llm_output
) )
return ans return ans
except: except:
@ -60,6 +100,3 @@ class CustomOutputParser(AgentOutputParser):
return_values={"output": f"调用agent失败: `{llm_output}`"}, return_values={"output": f"调用agent失败: `{llm_output}`"},
log=llm_output, log=llm_output,
) )

View File

@ -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.prompts import PromptTemplate
from langchain.chains import LLMMathChain 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 configs.model_config import LLM_MODEL, TEMPERATURE
from langchain.chat_models import ChatOpenAI _PROMPT_TEMPLATE = """
from langchain.callbacks.manager import CallbackManagerForToolRun 将数学问题翻译成可以使用Python的numexpr库执行的表达式使用运行此代码的输出来回答问题
_PROMPT_TEMPLATE = """将数学问题翻译成可以使用Python的numexpr库执行的表达式。使用运行此代码的输出来回答问题。
问题: ${{包含数学问题的问题}} 问题: ${{包含数学问题的问题}}
```text ```text
${{解决问题的单行数学表达式}} ${{解决问题的单行数学表达式}}
@ -68,3 +71,8 @@ def calculate(query: str):
llm_math = LLMMathChain.from_llm(model, verbose=True, prompt=PROMPT) llm_math = LLMMathChain.from_llm(model, verbose=True, prompt=PROMPT)
ans = llm_math.run(query) ans = llm_math.run(query)
return ans return ans
if __name__ == "__main__":
result = calculate("2的三次方")
print("答案:",result)

View File

@ -20,12 +20,12 @@ tools = [
Tool.from_function( Tool.from_function(
func=translate, func=translate,
name="翻译工具", name="翻译工具",
description="翻译各种语言" description="如果你无法访问互联网,并且需要翻译各种语言,应该使用这个工具"
), ),
Tool.from_function( Tool.from_function(
func=weathercheck, func=weathercheck,
name="天气查询工具", name="天气查询工具",
description="查询天气", description="如果你无法访问互联网,并需要查询中国各地未来24小时的天气,你应该使用这个工具,每轮对话仅能使用一次",
), ),
Tool.from_function( Tool.from_function(
func=shell, func=shell,
@ -35,12 +35,12 @@ tools = [
Tool.from_function( Tool.from_function(
func=search_knowledge, func=search_knowledge,
name="知识库查询工具", name="知识库查询工具",
description="使用西交利物浦大学大数据专业的本专业数据库来解答问题", description="访问知识库来获取答案",
), ),
Tool.from_function( Tool.from_function(
func=search_internet, func=search_internet,
name="互联网查询工具", name="互联网查询工具",
description="访问Bing互联网来解答问题", description="如果你无法访问互联网,这个工具可以帮助你访问Bing互联网来解答问题",
), ),
] ]

View File

@ -1,11 +1,10 @@
from langchain.prompts import PromptTemplate ## 单独运行的时候需要添加
from langchain.chains import LLMChain
import sys import sys
import os import os
from server.utils import get_ChatOpenAI
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 langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from server.utils import get_ChatOpenAI
from langchain.chains.llm_math.prompt import PROMPT from langchain.chains.llm_math.prompt import PROMPT
from configs.model_config import LLM_MODEL,TEMPERATURE from configs.model_config import LLM_MODEL,TEMPERATURE
@ -16,25 +15,12 @@ _PROMPT_TEMPLATE = '''
2. 无论提供的是陈述句或疑问句只进行翻译 2. 无论提供的是陈述句或疑问句只进行翻译
3. 不添加与原文无关的内容 3. 不添加与原文无关的内容
原文: ${{用户需要翻译的原文和目标语言}} 问题: ${{用户需要翻译的原文和目标语言}}
{question} 答案: 你翻译结果
```output
${{翻译结果}} 现在这是我的问题
``` 问题: {question}
答案: ${{答案}}
以下是两个例子
问题: 翻译13成英语
```text
13 英语
```output
thirteen
以下是两个例子
问题: 翻译 我爱你 成法语
```text
13 法语
```output
Je t'aime.
''' '''
PROMPT = PromptTemplate( PROMPT = PromptTemplate(
@ -51,5 +37,8 @@ def translate(query: str):
) )
llm_translate = LLMChain(llm=model, prompt=PROMPT) llm_translate = LLMChain(llm=model, prompt=PROMPT)
ans = llm_translate.run(query) ans = llm_translate.run(query)
return ans
return ans if __name__ == "__main__":
result = translate("Can Love remember the question and the answer? 这句话如何诗意的翻译成中文")
print("答案:",result)

View File

@ -1,10 +1,12 @@
## 使用和风天气API查询天气 ## 使用和风天气API查询天气,这个模型仅仅对免费的API进行了适配
## 这个模型的提示词非常复杂我们推荐使用GPT4模型进行运行
from __future__ import annotations from __future__ import annotations
## 单独运行的时候需要添加 ## 单独运行的时候需要添加
import sys import sys
import os 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 from server.utils import get_ChatOpenAI
@ -26,9 +28,71 @@ from langchain.schema.language_model import BaseLanguageModel
import requests import requests
from typing import List, Any, Optional from typing import List, Any, Optional
from configs.model_config import LLM_MODEL, TEMPERATURE from configs.model_config import LLM_MODEL, TEMPERATURE
from datetime import datetime
from langchain.prompts import PromptTemplate
## 使用和风天气API查询天气 ## 使用和风天气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): def get_city_info(location, adm, key):
base_url = 'https://geoapi.qweather.com/v2/city/lookup?' base_url = 'https://geoapi.qweather.com/v2/city/lookup?'
@ -38,12 +102,9 @@ def get_city_info(location, adm, key):
return data return data
from datetime import datetime def format_weather_data(data,place):
def format_weather_data(data):
hourly_forecast = data['hourly'] hourly_forecast = data['hourly']
formatted_data = '' formatted_data = f"\n 这是查询到的关于{place}未来24小时的天气信息: \n"
for forecast in hourly_forecast: for forecast in hourly_forecast:
# 将预报时间转换为datetime对象 # 将预报时间转换为datetime对象
forecast_time = datetime.strptime(forecast['fxTime'], '%Y-%m-%dT%H:%M%z') 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: elif hours_diff >= 24:
# 如果超过24小时转换为天数 # 如果超过24小时转换为天数
days_diff = hours_diff // 24 days_diff = hours_diff // 24
hours_diff_str = str(int(days_diff)) + '' hours_diff_str = str(int(days_diff)) + ''
else: 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 + ' 距离现在有: ' + hours_diff_str + '\n'
formatted_data += '具体时间: ' + forecast_time_str + '\n'
formatted_data += '温度: ' + forecast['temp'] + '°C\n' formatted_data += '温度: ' + forecast['temp'] + '°C\n'
formatted_data += '天气: ' + forecast['text'] + '\n' formatted_data += '天气: ' + forecast['text'] + '\n'
formatted_data += '风向: ' + forecast['windDir'] + '\n' formatted_data += '风向: ' + forecast['windDir'] + '\n'
@ -84,53 +144,54 @@ def format_weather_data(data):
formatted_data += '湿度: ' + forecast['humidity'] + '%\n' formatted_data += '湿度: ' + forecast['humidity'] + '%\n'
formatted_data += '降水概率: ' + forecast['pop'] + '%\n' formatted_data += '降水概率: ' + forecast['pop'] + '%\n'
# formatted_data += '降水量: ' + forecast['precip'] + 'mm\n' # formatted_data += '降水量: ' + forecast['precip'] + 'mm\n'
formatted_data += '\n\n' formatted_data += '\n'
return formatted_data return formatted_data
def get_weather(key, location_id, time: str = "24"): def get_weather(key, location_id,place):
if time: url = "https://devapi.qweather.com/v7/weather/24h?"
url = "https://devapi.qweather.com/v7/weather/" + time + "h?"
else:
time = "3" # 免费订阅只能查看3天的天气
url = "https://devapi.qweather.com/v7/weather/" + time + "d?"
params = { params = {
'location': location_id, 'location': location_id,
'key': key, 'key': key,
} }
response = requests.get(url, params=params) response = requests.get(url, params=params)
data = response.json() data = response.json()
return format_weather_data(data) return format_weather_data(data,place)
def split_query(query): def split_query(query):
parts = query.split() parts = query.split()
location = parts[0] if parts[0] != 'None' else parts[1] adm = parts[0]
adm = parts[1] location = parts[1] if parts[1] != 'None' else adm
time = parts[2] return location, adm
return location, adm, time
def weather(query): def weather(query):
location, adm, time = split_query(query) location, adm= split_query(query)
key = KEY key = KEY
if time != "None" and int(time) > 24:
return "只能查看24小时内的天气无法回答"
if time == "None":
time = "24" # 免费的版本只能24小时内的天气
if key == "": if key == "":
return "请先在代码中填入和风天气API Key" return "请先在代码中填入和风天气API Key"
city_info = get_city_info(location=location, adm=adm, key=key) try:
location_id = city_info['location'][0]['id'] city_info = get_city_info(location=location, adm=adm, key=key)
weather_data = get_weather(key=key, location_id=location_id, time=time) location_id = city_info['location'][0]['id']
return weather_data 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): class LLMWeatherChain(Chain):
llm_chain: LLMChain llm_chain: LLMChain
llm: Optional[BaseLanguageModel] = None llm: Optional[BaseLanguageModel] = None
"""[Deprecated] LLM wrapper to use.""" """[Deprecated] LLM wrapper to use."""
prompt: BasePromptTemplate prompt: BasePromptTemplate = PROMPT
"""[Deprecated] Prompt to use to translate to python if necessary.""" """[Deprecated] Prompt to use to translate to python if necessary."""
input_key: str = "question" #: :meta private: input_key: str = "question" #: :meta private:
output_key: str = "answer" #: :meta private: output_key: str = "answer" #: :meta private:
@ -175,7 +236,8 @@ class LLMWeatherChain(Chain):
output = weather(expression) output = weather(expression)
except Exception as e: except Exception as e:
output = "输入的信息有误,请再次尝试" output = "输入的信息有误,请再次尝试"
# raise ValueError(f"错误: {expression},输入的信息不对") return {self.output_key: output}
raise ValueError(f"错误: {expression},输入的信息不对")
return output return output
@ -198,7 +260,8 @@ class LLMWeatherChain(Chain):
elif "Answer:" in llm_output: elif "Answer:" in llm_output:
answer = "Answer: " + llm_output.split("Answer:")[-1] answer = "Answer: " + llm_output.split("Answer:")[-1]
else: 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} return {self.output_key: answer}
async def _aprocess_llm_result( async def _aprocess_llm_result(
@ -259,92 +322,13 @@ class LLMWeatherChain(Chain):
def from_llm( def from_llm(
cls, cls,
llm: BaseLanguageModel, llm: BaseLanguageModel,
prompt: BasePromptTemplate, prompt: BasePromptTemplate = PROMPT,
**kwargs: Any, **kwargs: Any,
) -> LLMWeatherChain: ) -> LLMWeatherChain:
llm_chain = LLMChain(llm=llm, prompt=prompt) llm_chain = LLMChain(llm=llm, prompt=prompt)
return cls(llm_chain=llm_chain, **kwargs) 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): def weathercheck(query: str):
model = get_ChatOpenAI( model = get_ChatOpenAI(
@ -357,9 +341,4 @@ def weathercheck(query: str):
return ans return ans
if __name__ == '__main__': if __name__ == '__main__':
result = weathercheck("苏州工姑苏区今晚热不热?")
## 检测api是否能正确返回
query = "上海浦东未来1小时天气情况"
# ans = weathercheck(query)
ans = weather("浦东 上海 1")
print(ans)

View File

@ -53,13 +53,11 @@ async def agent_chat(query: str = Body(..., description="用户输入", examples
agent = LLMSingleActionAgent( agent = LLMSingleActionAgent(
llm_chain=llm_chain, llm_chain=llm_chain,
output_parser=output_parser, output_parser=output_parser,
stop=["Observation:", "Observation:\n", "<|im_end|>"], # Qwen模型中使用这个 stop=["\nObservation:", "Observation:", "<|im_end|>"], # Qwen模型中使用这个
# stop=["Observation:", "Observation:\n"], # 其他模型,注意模板
allowed_tools=tool_names, allowed_tools=tool_names,
) )
# 把history转成agent的memory # 把history转成agent的memory
memory = ConversationBufferWindowMemory(k=HISTORY_LEN * 2) memory = ConversationBufferWindowMemory(k=HISTORY_LEN * 2)
for message in history: for message in history:
# 检查消息的角色 # 检查消息的角色
if message.role == 'user': if message.role == 'user':
@ -74,29 +72,41 @@ async def agent_chat(query: str = Body(..., description="用户输入", examples
memory=memory, memory=memory,
) )
input_msg = History(role="user", content="{{ input }}").to_msg_template(False) input_msg = History(role="user", content="{{ input }}").to_msg_template(False)
task = asyncio.create_task(wrap_done( while True:
agent_executor.acall(query, callbacks=[callback], include_run_info=True), try:
callback.done), task = asyncio.create_task(wrap_done(
) agent_executor.acall(query, callbacks=[callback], include_run_info=True),
callback.done))
break
except:
pass
if stream: if stream:
async for chunk in callback.aiter(): async for chunk in callback.aiter():
tools_use = [] tools_use = []
# Use server-sent-events to stream the response # Use server-sent-events to stream the response
data = json.loads(chunk) 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: if data["status"] == Status.start or data["status"] == Status.complete:
continue continue
if data["status"] == Status.agent_action: if data["status"] == Status.error:
yield json.dumps({"answer": "(正在使用工具,请注意工具栏变化) \n\n"}, ensure_ascii=False)
if data["status"] == Status.agent_finish:
tools_use.append("工具名称: " + data["tool_name"]) 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["input_str"])
tools_use.append("工具输出: " + data["output_str"]) tools_use.append("工具输出: " + data["output_str"])
tools_use.append("\n```\n")
yield json.dumps({"tools": tools_use}, ensure_ascii=False) 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: else:
pass pass

View File

@ -44,7 +44,8 @@ class ApiModelWorker(BaseModelWorker):
def count_token(self, params): def count_token(self, params):
# TODO需要完善 # TODO需要完善
print("count token") # print("count token")
print("\n\n\n")
print(params) print(params)
prompt = params["prompt"] prompt = params["prompt"]
return {"count": len(str(prompt)), "error_code": 0} return {"count": len(str(prompt)), "error_code": 0}

View File

@ -26,10 +26,10 @@ class ChatGLMWorker(ApiModelWorker):
# 这里的是chatglm api的模板其它API的conv_template需要定制 # 这里的是chatglm api的模板其它API的conv_template需要定制
self.conv = conv.Conversation( self.conv = conv.Conversation(
name=self.model_names[0], name=self.model_names[0],
system_message="你是一个聪明、对人类有帮助的人工智能,你可以对人类提出的问题给出有用、详细、礼貌的回答。", system_message="你是一个聪明的助手,请根据用户的提示来完成任务",
messages=[], messages=[],
roles=["Human", "Assistant"], roles=["Human", "Assistant"],
sep="\n### ", sep="\n###",
stop_str="###", stop_str="###",
) )
@ -57,7 +57,7 @@ class ChatGLMWorker(ApiModelWorker):
def get_embeddings(self, params): def get_embeddings(self, params):
# TODO: 支持embeddings # TODO: 支持embeddings
print("embedding") print("embedding")
print(params) # print(params)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -158,7 +158,7 @@ def create_model_worker_app(log_level: str = "INFO", **kwargs) -> FastAPI:
else: else:
from fastchat.serve.model_worker import app, GptqConfig, AWQConfig, ModelWorker from fastchat.serve.model_worker import app, GptqConfig, AWQConfig, ModelWorker
args.gpus = "0" # GPU的编号,如果有多个GPU可以设置为"0,1,2,3" 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.num_gpus = 1 # model worker的切分是model并行这里填写显卡的数量
args.load_8bit = False 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_ckpt = None
args.awq_wbits = 16 args.awq_wbits = 16
args.awq_groupsize = -1 args.awq_groupsize = -1
args.model_names = [] args.model_names = [""]
args.conv_template = None args.conv_template = None
args.limit_worker_concurrency = 5 args.limit_worker_concurrency = 5
args.stream_interval = 2 args.stream_interval = 2

View File

@ -7,7 +7,6 @@ import os
from configs import LLM_MODEL, TEMPERATURE from configs import LLM_MODEL, TEMPERATURE
from server.utils import get_model_worker_config from server.utils import get_model_worker_config
from typing import List, Dict from typing import List, Dict
chat_box = ChatBox( chat_box = ChatBox(
assistant_avatar=os.path.join( assistant_avatar=os.path.join(
"img", "img",
@ -16,6 +15,9 @@ chat_box = ChatBox(
) )
def get_messages_history(history_len: int, content_in_expander: bool = False) -> List[Dict]: 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) temperature = st.slider("Temperature", 0.0, 1.0, TEMPERATURE, 0.05)
history_len = st.number_input("历史对话轮数:", 0, 20, HISTORY_LEN) history_len = st.number_input("历史对话轮数:", 0, 20, HISTORY_LEN)
LLM_MODEL_WEBUI = llm_model
TEMPERATURE_WEBUI = temperature
def on_kb_change(): def on_kb_change():
st.toast(f"已加载知识库: {st.session_state.selected_kb}") st.toast(f"已加载知识库: {st.session_state.selected_kb}")
@ -155,9 +159,17 @@ def dialogue_page(api: ApiRequest):
elif dialogue_mode == "自定义Agent问答": elif dialogue_mode == "自定义Agent问答":
chat_box.ai_say([ chat_box.ai_say([
f"正在思考和寻找工具 ...",]) f"正在思考...",
Markdown("...", in_expander=True, title="思考过程", state="complete"),
])
text = "" 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 <span style='color:red'>改模型并没有进行Agent对齐无法正常使用Agent功能</span>\n\n\n<span style='color:red'>请更换 GPT4或Qwen-14B等支持Agent的模型获得更好的体验 </span> \n\n\n"
chat_box.update_msg(ans, element_index=0, streaming=False)
for d in api.agent_chat(prompt, for d in api.agent_chat(prompt,
history=history, history=history,
model=llm_model, model=llm_model,
@ -169,14 +181,17 @@ def dialogue_page(api: ApiRequest):
if error_msg := check_error_msg(d): # check whether error occured if error_msg := check_error_msg(d): # check whether error occured
st.error(error_msg) 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"): elif chunk := d.get("answer"):
text += chunk text += chunk
chat_box.update_msg(text, element_index=0) chat_box.update_msg(text, element_index=1)
elif chunk := d.get("tools"): elif chunk := d.get("tools"):
element_index += 1 text += "\n\n".join(d.get("tools", []))
chat_box.insert_msg(Markdown("...", in_expander=True, title="使用工具...", state="complete")) chat_box.update_msg(text, element_index=1)
chat_box.update_msg("\n\n".join(d.get("tools", [])), element_index=element_index, streaming=False) chat_box.update_msg(ans, element_index=0, streaming=False)
chat_box.update_msg(text, element_index=0, streaming=False) chat_box.update_msg(text, element_index=1, streaming=False)
elif dialogue_mode == "知识库问答": elif dialogue_mode == "知识库问答":
chat_box.ai_say([ chat_box.ai_say([
f"正在查询知识库 `{selected_kb}` ...", f"正在查询知识库 `{selected_kb}` ...",

View File

@ -250,7 +250,7 @@ class ApiRequest:
logger.error(f'{e.__class__.__name__}: {msg}', logger.error(f'{e.__class__.__name__}: {msg}',
exc_info=e if log_verbose else None) exc_info=e if log_verbose else None)
else: else:
print(chunk, end="", flush=True) # print(chunk, end="", flush=True)
yield chunk yield chunk
except httpx.ConnectError as e: except httpx.ConnectError as e:
msg = f"无法连接API服务器请确认 api.py 已正常启动。({e})" msg = f"无法连接API服务器请确认 api.py 已正常启动。({e})"