web_ui.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. import os
  2. import pprint
  3. import re
  4. from typing import List, Optional, Union
  5. from qwen_agent import Agent, MultiAgentHub
  6. from qwen_agent.agents.user_agent import PENDING_USER_INPUT
  7. from qwen_agent.gui.gradio_utils import format_cover_html
  8. from qwen_agent.gui.utils import convert_fncall_to_text, convert_history_to_chatbot, get_avatar_image
  9. from qwen_agent.llm.schema import CONTENT, FILE, IMAGE, NAME, ROLE, USER, Message
  10. from qwen_agent.log import logger
  11. from qwen_agent.utils.utils import print_traceback
  12. class WebUI:
  13. """A Common chatbot application for agent."""
  14. def __init__(self, agent: Union[Agent, MultiAgentHub, List[Agent]], chatbot_config: Optional[dict] = None):
  15. """
  16. Initialization the chatbot.
  17. Args:
  18. agent: The agent or a list of agents,
  19. supports various types of agents such as Assistant, GroupChat, Router, etc.
  20. chatbot_config: The chatbot configuration.
  21. Set the configuration as {'user.name': '', 'user.avatar': '', 'agent.avatar': '', 'input.placeholder': '', 'prompt.suggestions': []}.
  22. """
  23. chatbot_config = chatbot_config or {}
  24. if isinstance(agent, MultiAgentHub):
  25. self.agent_list = [agent for agent in agent.nonuser_agents]
  26. self.agent_hub = agent
  27. elif isinstance(agent, list):
  28. self.agent_list = agent
  29. self.agent_hub = None
  30. else:
  31. self.agent_list = [agent]
  32. self.agent_hub = None
  33. user_name = chatbot_config.get('user.name', 'user')
  34. self.user_config = {
  35. 'name': user_name,
  36. 'avatar': chatbot_config.get(
  37. 'user.avatar',
  38. get_avatar_image(user_name),
  39. ),
  40. }
  41. self.agent_config_list = [{
  42. 'name': agent.name,
  43. 'avatar': chatbot_config.get(
  44. 'agent.avatar',
  45. get_avatar_image(agent.name),
  46. ),
  47. 'description': agent.description or "I'm a helpful assistant.",
  48. } for agent in self.agent_list]
  49. self.input_placeholder = chatbot_config.get('input.placeholder', '跟我聊聊吧~')
  50. self.prompt_suggestions = chatbot_config.get('prompt.suggestions', [])
  51. self.verbose = chatbot_config.get('verbose', False)
  52. """
  53. Run the chatbot.
  54. Args:
  55. messages: The chat history.
  56. """
  57. def run(self,
  58. messages: List[Message] = None,
  59. share: bool = False,
  60. server_name: str = None,
  61. server_port: int = None,
  62. concurrency_limit: int = 10,
  63. enable_mention: bool = False,
  64. **kwargs):
  65. self.run_kwargs = kwargs
  66. from qwen_agent.gui.gradio import gr, mgr
  67. customTheme = gr.themes.Default(
  68. primary_hue=gr.themes.utils.colors.blue,
  69. radius_size=gr.themes.utils.sizes.radius_none,
  70. )
  71. with gr.Blocks(
  72. css=os.path.join(os.path.dirname(__file__), 'assets/appBot.css'),
  73. theme=customTheme,
  74. ) as demo:
  75. history = gr.State([])
  76. with gr.Row(elem_classes='container'):
  77. with gr.Column(scale=4):
  78. chatbot = mgr.Chatbot(
  79. value=convert_history_to_chatbot(messages=messages),
  80. avatar_images=[
  81. self.user_config,
  82. self.agent_config_list,
  83. ],
  84. height=900,
  85. avatar_image_width=80,
  86. flushing=False,
  87. show_copy_button=True,
  88. )
  89. input = mgr.MultimodalInput(placeholder=self.input_placeholder,)
  90. with gr.Column(scale=1):
  91. if len(self.agent_list) > 1:
  92. agent_selector = gr.Dropdown(
  93. [(agent.name, i) for i, agent in enumerate(self.agent_list)],
  94. label='Agents',
  95. info='选择一个Agent',
  96. value=0,
  97. interactive=True,
  98. )
  99. agent_info_block = self._create_agent_info_block()
  100. agent_plugins_block = self._create_agent_plugins_block()
  101. if self.prompt_suggestions:
  102. gr.Examples(
  103. label='推荐对话',
  104. examples=self.prompt_suggestions,
  105. inputs=[input],
  106. )
  107. if len(self.agent_list) > 1:
  108. agent_selector.change(
  109. fn=self.change_agent,
  110. inputs=[agent_selector],
  111. outputs=[agent_selector, agent_info_block, agent_plugins_block],
  112. queue=False,
  113. )
  114. input_promise = input.submit(
  115. fn=self.add_text,
  116. inputs=[input, chatbot, history],
  117. outputs=[input, chatbot, history],
  118. queue=False,
  119. )
  120. if len(self.agent_list) > 1 and enable_mention:
  121. input_promise = input_promise.then(
  122. self.add_mention,
  123. [chatbot, agent_selector],
  124. [chatbot, agent_selector],
  125. ).then(
  126. self.agent_run,
  127. [chatbot, history, agent_selector],
  128. [chatbot, history, agent_selector],
  129. )
  130. else:
  131. input_promise = input_promise.then(
  132. self.agent_run,
  133. [chatbot, history],
  134. [chatbot, history],
  135. )
  136. input_promise.then(self.flushed, None, [input])
  137. demo.load(None)
  138. demo.queue(default_concurrency_limit=concurrency_limit).launch(share=share,
  139. server_name=server_name,
  140. server_port=server_port)
  141. def change_agent(self, agent_selector):
  142. yield agent_selector, self._create_agent_info_block(agent_selector), self._create_agent_plugins_block(
  143. agent_selector)
  144. def add_text(self, _input, _chatbot, _history):
  145. _history.append({
  146. ROLE: USER,
  147. CONTENT: [{
  148. 'text': _input.text
  149. }],
  150. })
  151. if self.user_config[NAME]:
  152. _history[-1][NAME] = self.user_config[NAME]
  153. if _input.files:
  154. for file in _input.files:
  155. if file.mime_type.startswith('image/'):
  156. _history[-1][CONTENT].append({IMAGE: 'file://' + file.path})
  157. else:
  158. _history[-1][CONTENT].append({FILE: file.path})
  159. _chatbot.append([_input, None])
  160. from qwen_agent.gui.gradio import gr
  161. yield gr.update(interactive=False, value=None), _chatbot, _history
  162. def add_mention(self, _chatbot, _agent_selector):
  163. if len(self.agent_list) == 1:
  164. yield _chatbot, _agent_selector
  165. query = _chatbot[-1][0].text
  166. match = re.search(r'@\w+\b', query)
  167. if match:
  168. _agent_selector = self._get_agent_index_by_name(match.group()[1:])
  169. agent_name = self.agent_list[_agent_selector].name
  170. if ('@' + agent_name) not in query and self.agent_hub is None:
  171. _chatbot[-1][0].text = '@' + agent_name + ' ' + query
  172. yield _chatbot, _agent_selector
  173. def agent_run(self, _chatbot, _history, _agent_selector=None):
  174. if self.verbose:
  175. logger.info('agent_run input:\n' + pprint.pformat(_history, indent=2))
  176. num_input_bubbles = len(_chatbot) - 1
  177. num_output_bubbles = 1
  178. _chatbot[-1][1] = [None for _ in range(len(self.agent_list))]
  179. agent_runner = self.agent_list[_agent_selector or 0]
  180. if self.agent_hub:
  181. agent_runner = self.agent_hub
  182. responses = []
  183. for responses in agent_runner.run(_history, **self.run_kwargs):
  184. if not responses:
  185. continue
  186. if responses[-1][CONTENT] == PENDING_USER_INPUT:
  187. logger.info('Interrupted. Waiting for user input!')
  188. break
  189. display_responses = convert_fncall_to_text(responses)
  190. if not display_responses:
  191. continue
  192. if display_responses[-1][CONTENT] is None:
  193. continue
  194. while len(display_responses) > num_output_bubbles:
  195. # Create a new chat bubble
  196. _chatbot.append([None, None])
  197. _chatbot[-1][1] = [None for _ in range(len(self.agent_list))]
  198. num_output_bubbles += 1
  199. assert num_output_bubbles == len(display_responses)
  200. assert num_input_bubbles + num_output_bubbles == len(_chatbot)
  201. for i, rsp in enumerate(display_responses):
  202. agent_index = self._get_agent_index_by_name(rsp[NAME])
  203. _chatbot[num_input_bubbles + i][1][agent_index] = rsp[CONTENT]
  204. if len(self.agent_list) > 1:
  205. _agent_selector = agent_index
  206. if _agent_selector is not None:
  207. yield _chatbot, _history, _agent_selector
  208. else:
  209. yield _chatbot, _history
  210. if responses:
  211. _history.extend([res for res in responses if res[CONTENT] != PENDING_USER_INPUT])
  212. if _agent_selector is not None:
  213. yield _chatbot, _history, _agent_selector
  214. else:
  215. yield _chatbot, _history
  216. if self.verbose:
  217. logger.info('agent_run response:\n' + pprint.pformat(responses, indent=2))
  218. def flushed(self):
  219. from qwen_agent.gui.gradio import gr
  220. return gr.update(interactive=True)
  221. def _get_agent_index_by_name(self, agent_name):
  222. if agent_name is None:
  223. return 0
  224. try:
  225. agent_name = agent_name.strip()
  226. for i, agent in enumerate(self.agent_list):
  227. if agent.name == agent_name:
  228. return i
  229. return 0
  230. except Exception:
  231. print_traceback()
  232. return 0
  233. def _create_agent_info_block(self, agent_index=0):
  234. from qwen_agent.gui.gradio import gr
  235. agent_config_interactive = self.agent_config_list[agent_index]
  236. return gr.HTML(
  237. format_cover_html(
  238. bot_name=agent_config_interactive['name'],
  239. bot_description=agent_config_interactive['description'],
  240. bot_avatar=agent_config_interactive['avatar'],
  241. ))
  242. def _create_agent_plugins_block(self, agent_index=0):
  243. from qwen_agent.gui.gradio import gr
  244. agent_interactive = self.agent_list[agent_index]
  245. if agent_interactive.function_map:
  246. capabilities = [key for key in agent_interactive.function_map.keys()]
  247. return gr.CheckboxGroup(
  248. label='插件',
  249. value=capabilities,
  250. choices=capabilities,
  251. interactive=False,
  252. )
  253. else:
  254. return gr.CheckboxGroup(
  255. label='插件',
  256. value=[],
  257. choices=[],
  258. interactive=False,
  259. )