Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
11 месяцев назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. # encoding:utf-8
  2. import json
  3. import time
  4. from typing import List, Tuple
  5. import openai
  6. import openai.error
  7. import broadscope_bailian
  8. from broadscope_bailian import ChatQaMessage
  9. from bot.bot import Bot
  10. from bot.ali.ali_qwen_session import AliQwenSession
  11. from bot.session_manager import SessionManager
  12. from bridge.context import ContextType
  13. from bridge.reply import Reply, ReplyType
  14. from common.log import logger
  15. from common import const
  16. from config import conf, load_config
  17. class AliQwenBot(Bot):
  18. def __init__(self):
  19. super().__init__()
  20. self.api_key_expired_time = self.set_api_key()
  21. self.sessions = SessionManager(AliQwenSession, model=conf().get("model", const.QWEN))
  22. def api_key_client(self):
  23. return broadscope_bailian.AccessTokenClient(access_key_id=self.access_key_id(), access_key_secret=self.access_key_secret())
  24. def access_key_id(self):
  25. return conf().get("qwen_access_key_id")
  26. def access_key_secret(self):
  27. return conf().get("qwen_access_key_secret")
  28. def agent_key(self):
  29. return conf().get("qwen_agent_key")
  30. def app_id(self):
  31. return conf().get("qwen_app_id")
  32. def node_id(self):
  33. return conf().get("qwen_node_id", "")
  34. def temperature(self):
  35. return conf().get("temperature", 0.2 )
  36. def top_p(self):
  37. return conf().get("top_p", 1)
  38. def reply(self, query, context=None):
  39. # acquire reply content
  40. if context.type == ContextType.TEXT:
  41. logger.info("[QWEN] query={}".format(query))
  42. session_id = context["session_id"]
  43. reply = None
  44. clear_memory_commands = conf().get("clear_memory_commands", ["#清除记忆"])
  45. if query in clear_memory_commands:
  46. self.sessions.clear_session(session_id)
  47. reply = Reply(ReplyType.INFO, "记忆已清除")
  48. elif query == "#清除所有":
  49. self.sessions.clear_all_session()
  50. reply = Reply(ReplyType.INFO, "所有人记忆已清除")
  51. elif query == "#更新配置":
  52. load_config()
  53. reply = Reply(ReplyType.INFO, "配置已更新")
  54. if reply:
  55. return reply
  56. session = self.sessions.session_query(query, session_id)
  57. logger.debug("[QWEN] session query={}".format(session.messages))
  58. reply_content = self.reply_text(session)
  59. logger.debug(
  60. "[QWEN] new_query={}, session_id={}, reply_cont={}, completion_tokens={}".format(
  61. session.messages,
  62. session_id,
  63. reply_content["content"],
  64. reply_content["completion_tokens"],
  65. )
  66. )
  67. if reply_content["completion_tokens"] == 0 and len(reply_content["content"]) > 0:
  68. reply = Reply(ReplyType.ERROR, reply_content["content"])
  69. elif reply_content["completion_tokens"] > 0:
  70. self.sessions.session_reply(reply_content["content"], session_id, reply_content["total_tokens"])
  71. reply = Reply(ReplyType.TEXT, reply_content["content"])
  72. else:
  73. reply = Reply(ReplyType.ERROR, reply_content["content"])
  74. logger.debug("[QWEN] reply {} used 0 tokens.".format(reply_content))
  75. return reply
  76. else:
  77. reply = Reply(ReplyType.ERROR, "Bot不支持处理{}类型的消息".format(context.type))
  78. return reply
  79. def reply_text(self, session: AliQwenSession, retry_count=0) -> dict:
  80. """
  81. call bailian's ChatCompletion to get the answer
  82. :param session: a conversation session
  83. :param retry_count: retry count
  84. :return: {}
  85. """
  86. try:
  87. prompt, history = self.convert_messages_format(session.messages)
  88. self.update_api_key_if_expired()
  89. # NOTE 阿里百炼的call()函数未提供temperature参数,考虑到temperature和top_p参数作用相同,取两者较小的值作为top_p参数传入,详情见文档 https://help.aliyun.com/document_detail/2587502.htm
  90. response = broadscope_bailian.Completions().call(app_id=self.app_id(), prompt=prompt, history=history, top_p=min(self.temperature(), self.top_p()))
  91. completion_content = self.get_completion_content(response, self.node_id())
  92. completion_tokens, total_tokens = self.calc_tokens(session.messages, completion_content)
  93. return {
  94. "total_tokens": total_tokens,
  95. "completion_tokens": completion_tokens,
  96. "content": completion_content,
  97. }
  98. except Exception as e:
  99. need_retry = retry_count < 2
  100. result = {"completion_tokens": 0, "content": "我现在有点累了,等会再来吧"}
  101. if isinstance(e, openai.error.RateLimitError):
  102. logger.warn("[QWEN] RateLimitError: {}".format(e))
  103. result["content"] = "提问太快啦,请休息一下再问我吧"
  104. if need_retry:
  105. time.sleep(20)
  106. elif isinstance(e, openai.error.Timeout):
  107. logger.warn("[QWEN] Timeout: {}".format(e))
  108. result["content"] = "我没有收到你的消息"
  109. if need_retry:
  110. time.sleep(5)
  111. elif isinstance(e, openai.error.APIError):
  112. logger.warn("[QWEN] Bad Gateway: {}".format(e))
  113. result["content"] = "请再问我一次"
  114. if need_retry:
  115. time.sleep(10)
  116. elif isinstance(e, openai.error.APIConnectionError):
  117. logger.warn("[QWEN] APIConnectionError: {}".format(e))
  118. need_retry = False
  119. result["content"] = "我连接不到你的网络"
  120. else:
  121. logger.exception("[QWEN] Exception: {}".format(e))
  122. need_retry = False
  123. self.sessions.clear_session(session.session_id)
  124. if need_retry:
  125. logger.warn("[QWEN] 第{}次重试".format(retry_count + 1))
  126. return self.reply_text(session, retry_count + 1)
  127. else:
  128. return result
  129. def set_api_key(self):
  130. api_key, expired_time = self.api_key_client().create_token(agent_key=self.agent_key())
  131. broadscope_bailian.api_key = api_key
  132. return expired_time
  133. def update_api_key_if_expired(self):
  134. if time.time() > self.api_key_expired_time:
  135. self.api_key_expired_time = self.set_api_key()
  136. def convert_messages_format(self, messages) -> Tuple[str, List[ChatQaMessage]]:
  137. history = []
  138. user_content = ''
  139. assistant_content = ''
  140. system_content = ''
  141. for message in messages:
  142. role = message.get('role')
  143. if role == 'user':
  144. user_content += message.get('content')
  145. elif role == 'assistant':
  146. assistant_content = message.get('content')
  147. history.append(ChatQaMessage(user_content, assistant_content))
  148. user_content = ''
  149. assistant_content = ''
  150. elif role =='system':
  151. system_content += message.get('content')
  152. if user_content == '':
  153. raise Exception('no user message')
  154. if system_content != '':
  155. # NOTE 模拟系统消息,测试发现人格描述以"你需要扮演ChatGPT"开头能够起作用,而以"你是ChatGPT"开头模型会直接否认
  156. system_qa = ChatQaMessage(system_content, '好的,我会严格按照你的设定回答问题')
  157. history.insert(0, system_qa)
  158. logger.debug("[QWEN] converted qa messages: {}".format([item.to_dict() for item in history]))
  159. logger.debug("[QWEN] user content as prompt: {}".format(user_content))
  160. return user_content, history
  161. def get_completion_content(self, response, node_id):
  162. if not response['Success']:
  163. return f"[ERROR]\n{response['Code']}:{response['Message']}"
  164. text = response['Data']['Text']
  165. if node_id == '':
  166. return text
  167. # TODO: 当使用流程编排创建大模型应用时,响应结构如下,最终结果在['finalResult'][node_id]['response']['text']中,暂时先这么写
  168. # {
  169. # 'Success': True,
  170. # 'Code': None,
  171. # 'Message': None,
  172. # 'Data': {
  173. # 'ResponseId': '9822f38dbacf4c9b8daf5ca03a2daf15',
  174. # 'SessionId': 'session_id',
  175. # 'Text': '{"finalResult":{"LLM_T7islK":{"params":{"modelId":"qwen-plus-v1","prompt":"${systemVars.query}${bizVars.Text}"},"response":{"text":"作为一个AI语言模型,我没有年龄,因为我没有生日。\n我只是一个程序,没有生命和身体。"}}}}',
  176. # 'Thoughts': [],
  177. # 'Debug': {},
  178. # 'DocReferences': []
  179. # },
  180. # 'RequestId': '8e11d31551ce4c3f83f49e6e0dd998b0',
  181. # 'Failed': None
  182. # }
  183. text_dict = json.loads(text)
  184. completion_content = text_dict['finalResult'][node_id]['response']['text']
  185. return completion_content
  186. def calc_tokens(self, messages, completion_content):
  187. completion_tokens = len(completion_content)
  188. prompt_tokens = 0
  189. for message in messages:
  190. prompt_tokens += len(message["content"])
  191. return completion_tokens, prompt_tokens + completion_tokens