api_server.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. #! /usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # vim:fenc=utf-8
  4. import time
  5. import logging
  6. import werkzeug.exceptions
  7. from flask import Flask, request, jsonify
  8. from argparse import ArgumentParser
  9. from pqai_agent import configs
  10. from pqai_agent import logging_service, chat_service, prompt_templates
  11. from pqai_agent.agents.message_reply_agent import MessageReplyAgent
  12. from pqai_agent.history_dialogue_service import HistoryDialogueService
  13. from pqai_agent.user_manager import MySQLUserManager, MySQLUserRelationManager
  14. from pqai_agent.utils.prompt_utils import format_agent_profile, format_user_profile
  15. from pqai_agent_server.const import AgentApiConst
  16. from pqai_agent_server.models import MySQLSessionManager
  17. from pqai_agent_server.utils import wrap_response, quit_human_intervention_status
  18. from pqai_agent_server.utils import (
  19. run_extractor_prompt,
  20. run_chat_prompt,
  21. run_response_type_prompt,
  22. )
  23. app = Flask('agent_api_server')
  24. logger = logging_service.logger
  25. const = AgentApiConst()
  26. @app.route('/api/listStaffs', methods=['GET'])
  27. def list_staffs():
  28. staff_data = app.user_relation_manager.list_staffs()
  29. return wrap_response(200, data=staff_data)
  30. @app.route('/api/getStaffProfile', methods=['GET'])
  31. def get_staff_profile():
  32. staff_id = request.args['staff_id']
  33. profile = app.user_manager.get_staff_profile(staff_id)
  34. if not profile:
  35. return wrap_response(404, msg='staff not found')
  36. else:
  37. return wrap_response(200, data=profile)
  38. @app.route('/api/getUserProfile', methods=['GET'])
  39. def get_user_profile():
  40. user_id = request.args['user_id']
  41. profile = app.user_manager.get_user_profile(user_id)
  42. if not profile:
  43. resp = {
  44. 'code': 404,
  45. 'msg': 'user not found'
  46. }
  47. else:
  48. resp = {
  49. 'code': 200,
  50. 'msg': 'success',
  51. 'data': profile
  52. }
  53. return jsonify(resp)
  54. @app.route('/api/listUsers', methods=['GET'])
  55. def list_users():
  56. user_name = request.args.get('user_name', None)
  57. user_union_id = request.args.get('user_union_id', None)
  58. if not user_name and not user_union_id:
  59. resp = {
  60. 'code': 400,
  61. 'msg': 'user_name or user_union_id is required'
  62. }
  63. return jsonify(resp)
  64. data = app.user_manager.list_users(user_name=user_name, user_union_id=user_union_id)
  65. return jsonify({'code': 200, 'data': data})
  66. @app.route('/api/getDialogueHistory', methods=['GET'])
  67. def get_dialogue_history():
  68. staff_id = request.args['staff_id']
  69. user_id = request.args['user_id']
  70. recent_minutes = int(request.args.get('recent_minutes', 1440))
  71. dialogue_history = app.history_dialogue_service.get_dialogue_history(staff_id, user_id, recent_minutes)
  72. return jsonify({'code': 200, 'data': dialogue_history})
  73. @app.route('/api/listModels', methods=['GET'])
  74. def list_models():
  75. models = [
  76. {
  77. 'model_type': 'openai_compatible',
  78. 'model_name': chat_service.VOLCENGINE_MODEL_DEEPSEEK_V3,
  79. 'display_name': 'DeepSeek V3 on 火山'
  80. },
  81. {
  82. 'model_type': 'openai_compatible',
  83. 'model_name': chat_service.VOLCENGINE_MODEL_DOUBAO_PRO_32K,
  84. 'display_name': '豆包Pro 32K'
  85. },
  86. {
  87. 'model_type': 'openai_compatible',
  88. 'model_name': chat_service.VOLCENGINE_MODEL_DOUBAO_PRO_1_5,
  89. 'display_name': '豆包Pro 1.5'
  90. },
  91. {
  92. 'model_type': 'openai_compatible',
  93. 'model_name': chat_service.VOLCENGINE_BOT_DEEPSEEK_V3_SEARCH,
  94. 'display_name': 'DeepSeek V3联网 on 火山'
  95. },
  96. {
  97. 'model_type': 'openai_compatible',
  98. 'model_name': chat_service.VOLCENGINE_MODEL_DOUBAO_1_5_VISION_PRO,
  99. 'display_name': '豆包1.5视觉理解Pro'
  100. },
  101. ]
  102. return wrap_response(200, data=models)
  103. @app.route('/api/listScenes', methods=['GET'])
  104. def list_scenes():
  105. scenes = [
  106. {'scene': 'greeting', 'display_name': '问候'},
  107. {'scene': 'chitchat', 'display_name': '闲聊'},
  108. {'scene': 'profile_extractor', 'display_name': '画像提取'},
  109. {'scene': 'response_type_detector', 'display_name': '回复模态判断'},
  110. {'scene': 'custom_debugging', 'display_name': '自定义调试场景'}
  111. ]
  112. return wrap_response(200, data=scenes)
  113. @app.route('/api/getBasePrompt', methods=['GET'])
  114. def get_base_prompt():
  115. scene = request.args['scene']
  116. prompt_map = {
  117. 'greeting': prompt_templates.GENERAL_GREETING_PROMPT,
  118. 'chitchat': prompt_templates.CHITCHAT_PROMPT_COZE,
  119. 'profile_extractor': prompt_templates.USER_PROFILE_EXTRACT_PROMPT,
  120. 'response_type_detector': prompt_templates.RESPONSE_TYPE_DETECT_PROMPT,
  121. 'custom_debugging': '',
  122. }
  123. model_map = {
  124. 'greeting': chat_service.VOLCENGINE_MODEL_DOUBAO_PRO_32K,
  125. 'chitchat': chat_service.VOLCENGINE_MODEL_DOUBAO_PRO_32K,
  126. 'profile_extractor': chat_service.VOLCENGINE_MODEL_DOUBAO_PRO_1_5,
  127. 'response_type_detector': chat_service.VOLCENGINE_MODEL_DOUBAO_PRO_1_5,
  128. 'custom_debugging': chat_service.VOLCENGINE_BOT_DEEPSEEK_V3_SEARCH
  129. }
  130. if scene not in prompt_map:
  131. return wrap_response(404, msg='scene not found')
  132. data = {
  133. 'model_name': model_map[scene],
  134. 'content': prompt_map[scene]
  135. }
  136. return wrap_response(200, data=data)
  137. @app.route('/api/runPrompt', methods=['POST'])
  138. def run_prompt():
  139. try:
  140. req_data = request.json
  141. logger.debug(req_data)
  142. scene = req_data['scene']
  143. if scene == 'profile_extractor':
  144. response = run_extractor_prompt(req_data)
  145. return wrap_response(200, data=response)
  146. elif scene == 'response_type_detector':
  147. response = run_response_type_prompt(req_data)
  148. return wrap_response(200, data=response.choices[0].message.content)
  149. else:
  150. response = run_chat_prompt(req_data)
  151. return wrap_response(200, data=response.choices[0].message.content)
  152. except Exception as e:
  153. logger.error(e)
  154. return wrap_response(500, msg='Error: {}'.format(e))
  155. @app.route('/api/formatForPrompt', methods=['POST'])
  156. def format_data_for_prompt():
  157. try:
  158. req_data = request.json
  159. content = req_data['content']
  160. format_type = req_data['format_type']
  161. if format_type == 'staff_profile':
  162. if not isinstance(content, dict):
  163. return wrap_response(400, msg='staff_profile should be a dict')
  164. response = format_agent_profile(content)
  165. elif format_type == 'user_profile':
  166. if not isinstance(content, dict):
  167. return wrap_response(400, msg='user_profile should be a dict')
  168. response = format_user_profile(content)
  169. elif format_type == 'dialogue':
  170. if not isinstance(content, list):
  171. return wrap_response(400, msg='dialogue should be a list')
  172. response = MessageReplyAgent.compose_dialogue(content)
  173. else:
  174. return wrap_response(400, msg='Invalid format_type')
  175. return wrap_response(200, data=response)
  176. except Exception as e:
  177. logger.error(e)
  178. return wrap_response(500, msg='Error: {}'.format(e))
  179. @app.route("/api/healthCheck", methods=["GET"])
  180. def health_check():
  181. return wrap_response(200, msg="OK")
  182. @app.route("/api/getStaffSessionSummary", methods=["GET"])
  183. def get_staff_session_summary():
  184. staff_id = request.args.get("staff_id")
  185. status = request.args.get("status", const.DEFAULT_STAFF_STATUS)
  186. page_id = request.args.get("page_id", const.DEFAULT_PAGE_ID)
  187. page_size = request.args.get("page_size", const.DEFAULT_PAGE_SIZE)
  188. # check params
  189. try:
  190. page_id = int(page_id)
  191. page_size = int(page_size)
  192. status = int(status)
  193. except Exception as e:
  194. return wrap_response(404, msg="Invalid parameter: {}".format(e))
  195. staff_session_summary = app.session_manager.get_staff_sessions_summary(
  196. staff_id, page_id, page_size, status
  197. )
  198. if not staff_session_summary:
  199. return wrap_response(404, msg="staff not found")
  200. else:
  201. return wrap_response(200, data=staff_session_summary)
  202. @app.route("/api/getStaffSessionList", methods=["GET"])
  203. def get_staff_session_list():
  204. staff_id = request.args.get("staff_id")
  205. if not staff_id:
  206. return wrap_response(404, msg="staff_id is required")
  207. page_size = request.args.get("page_size", const.DEFAULT_PAGE_SIZE)
  208. page_id = request.args.get("page_id", const.DEFAULT_PAGE_ID)
  209. staff_session_list = app.session_manager.get_staff_session_list(staff_id, page_id, page_size)
  210. if not staff_session_list:
  211. return wrap_response(404, msg="staff not found")
  212. return wrap_response(200, data=staff_session_list)
  213. @app.route("/api/getStaffList", methods=["GET"])
  214. def get_staff_list():
  215. page_size = request.args.get("page_size", const.DEFAULT_PAGE_SIZE)
  216. page_id = request.args.get("page_id", const.DEFAULT_PAGE_ID)
  217. staff_list = app.user_manager.get_staff_list(page_id, page_size)
  218. if not staff_list:
  219. return wrap_response(404, msg="staff not found")
  220. return wrap_response(200, data=staff_list)
  221. @app.route("/api/getConversationList", methods=["GET"])
  222. def get_conversation_list():
  223. """
  224. 获取staff && user 私聊对话列表
  225. :return:
  226. """
  227. staff_id = request.args.get("staff_id")
  228. user_id = request.args.get("user_id")
  229. if not staff_id or not user_id:
  230. return wrap_response(404, msg="staff_id and user_id are required")
  231. page = request.args.get("page")
  232. response = app.session_manager.get_conversation_list(staff_id, user_id, page, const.DEFAULT_CONVERSATION_SIZE)
  233. return wrap_response(200, data=response)
  234. @app.route("/api/sendMessage", methods=["POST"])
  235. def send_message():
  236. return wrap_response(200, msg="暂不实现功能")
  237. @app.route("/api/quitHumanInterventionStatus", methods=["POST"])
  238. def quit_human_interventions_status():
  239. """
  240. 退出人工介入状态
  241. :return:
  242. """
  243. req_data = request.json
  244. staff_id = req_data["staff_id"]
  245. user_id = req_data["user_id"]
  246. if not user_id or not staff_id:
  247. return wrap_response(404, msg="user_id and staff_id are required")
  248. response = quit_human_intervention_status(user_id, staff_id)
  249. return wrap_response(200, data=response)
  250. @app.errorhandler(werkzeug.exceptions.BadRequest)
  251. def handle_bad_request(e):
  252. logger.error(e)
  253. return wrap_response(400, msg='Bad Request: {}'.format(e.description))
  254. if __name__ == '__main__':
  255. parser = ArgumentParser()
  256. parser.add_argument('--prod', action='store_true')
  257. parser.add_argument('--host', default='127.0.0.1')
  258. parser.add_argument('--port', type=int, default=8083)
  259. parser.add_argument('--log-level', default='INFO')
  260. args = parser.parse_args()
  261. config = configs.get()
  262. logging_level = logging.getLevelName(args.log_level)
  263. logging_service.setup_root_logger(level=logging_level, logfile_name='agent_api_server.log')
  264. # set db config
  265. user_db_config = config['storage']['user']
  266. staff_db_config = config['storage']['staff']
  267. agent_state_db_config = config['storage']['agent_state']
  268. chat_history_db_config = config['storage']['chat_history']
  269. # init user manager
  270. user_manager = MySQLUserManager(user_db_config['mysql'], user_db_config['table'], staff_db_config['table'])
  271. app.user_manager = user_manager
  272. # init session manager
  273. session_manager = MySQLSessionManager(
  274. db_config=user_db_config['mysql'],
  275. staff_table=staff_db_config['table'],
  276. user_table=user_db_config['table'],
  277. agent_state_table=agent_state_db_config['table'],
  278. chat_history_table=chat_history_db_config['table']
  279. )
  280. app.session_manager = session_manager
  281. wecom_db_config = config['storage']['user_relation']
  282. user_relation_manager = MySQLUserRelationManager(
  283. user_db_config['mysql'], wecom_db_config['mysql'],
  284. config['storage']['staff']['table'],
  285. user_db_config['table'],
  286. wecom_db_config['table']['staff'],
  287. wecom_db_config['table']['relation'],
  288. wecom_db_config['table']['user']
  289. )
  290. app.user_relation_manager = user_relation_manager
  291. app.history_dialogue_service = HistoryDialogueService(
  292. config['storage']['history_dialogue']['api_base_url']
  293. )
  294. app.run(debug=not args.prod, host=args.host, port=args.port)