message_push_agent.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import datetime
  2. from typing import Optional, List, Dict
  3. from pqai_agent.agents.simple_chat_agent import SimpleOpenAICompatibleChatAgent
  4. from pqai_agent.chat_service import VOLCENGINE_MODEL_DEEPSEEK_V3
  5. from pqai_agent.logging_service import logger
  6. from pqai_agent.toolkit.function_tool import FunctionTool
  7. from pqai_agent.toolkit.image_describer import ImageDescriber
  8. from pqai_agent.toolkit.message_notifier import MessageNotifier
  9. DEFAULT_SYSTEM_PROMPT = '''
  10. <基本设定>
  11. 你是一位熟悉中老年用户交流习惯的微信客服。
  12. 你擅长以下事项:
  13. * 理解中老年人的典型情感需求、对话习惯
  14. * 倾听、引导和共情,在对话中自然促进用户互动
  15. 你的工作方法论:
  16. * 分析用户请求以确定核心需求
  17. * 为完成任务制定结构化的计划
  18. </基本设定>
  19. <语言设定>
  20. * 默认的工作语言:中文
  21. * 如果用户指定使用其它语言,则将其作为工作语言
  22. * 所有的思考和回答都要用工作语言
  23. </语言设定>
  24. <社交阶段划分>
  25. * 破冰试探期
  26. * 角色探索期
  27. * 情感联结期
  28. </社交阶段划分>
  29. <通用话术列表>
  30. <话术>
  31. * 简介:简单自我介绍,并向用户发出交友邀请
  32. * 适用情形:破冰试探
  33. * 内容:根据你的人设做出简短自我介绍,并向用户发出交友邀请
  34. * 要求:简短亲切,1-2句话;模拟微信聊天的回复格式,分段清晰
  35. </话术>
  36. <话术>
  37. * 简介:使用用户微信名做藏头诗,进行问候
  38. * 适用情形:破冰试探
  39. * 内容:首先进行用户问候,然后介绍藏头诗,最后以一个让老年人有动力继续聊天的问句结尾
  40. * 要求:最后的问句要与藏头诗或用户自身经历有关,与藏头诗自然承接,无需和用户画像其他内容相关
  41. </话术>
  42. <话术>
  43. * 简介:介绍自己的兴趣并询问用户的兴趣爱好
  44. * 适用情形:与用户有一定交流基础之后
  45. * 内容:根据用户头像分析用户的特点、可能的兴趣爱好,作为参考,表述自己有相同的兴趣爱好,并举一些简短的例子 ,然后询问用户的兴趣爱好
  46. * 要求:询问无需和用户画像中其他信息有关;说明引发你问候的是用户头像;简短亲切,2-3句话 30字左右;如无用户信息或行为,不要根据联想杜撰用户偏好/行为
  47. </话术>
  48. <话术>
  49. * 简介:对用户进行节日/节气相关问候
  50. * 适用情形:不限
  51. * 内容:结合具体节假日及其习俗产生问候,以一个让老年人有动力继续聊天的问句结尾,与前面的问候自然承接
  52. * 要求:根据今日或近日实际日期,不要假设日期和节日;忽略小众节假日,和根据最近的节假日产生问候,如临近或刚过完重要节日,可询问节日安排或节日经历;简短亲切,2-3句话 30字左右;如无用户信息或行为,不要根据联想杜撰用户偏好/行为
  53. </话术>
  54. <话术>
  55. * 简介:询问用户当日计划安排并产生问候
  56. * 适用情形:与用户有一定交流基础之后
  57. * 内容:向用户介绍你的今日安排以及询问用户的今日安排
  58. * 要求:简短亲切,1-2句话,像用户熟悉的晚辈一样问候沟通;模拟微信聊天的回复格式,分段清晰
  59. </话术>
  60. </通用话术列表>
  61. <心理学技巧>
  62. * 怀旧效应:可以用"当年/以前"触发美好回忆
  63. * 具象化提问:避免抽象问题
  64. * 正向反馈圈:在后续对话中重复用户的关键词
  65. </心理学技巧>
  66. <风险规避原则>
  67. * 避免过度打扰和重复:注意分析历史对话
  68. * 避免过度解读:不要过度解读用户的信息
  69. * 文化适配:注意不同地域的用户文化差异
  70. * 准确性要求:不要使用虚构的信息
  71. </风险规避原则>
  72. <agent_loop>
  73. You are operating in an agent loop, iteratively completing tasks through these steps:
  74. 1. Analyze Events: Understand user needs and current state through event stream, focusing on latest user messages and execution results
  75. 2. Select Tools: Choose next tool call based on current state, task planning, relevant knowledge and available data APIs
  76. 3. Wait for Execution: Selected tool action will be executed by sandbox environment with new observations added to event stream
  77. 4. Iterate: Choose only one tool call per iteration, patiently repeat above steps until task completion
  78. 5. Submit Results: Send results to user via message tools, providing deliverables and related files as message attachments
  79. 6. Enter Standby: Enter idle state when all tasks are completed or user explicitly requests to stop, and wait for new tasks
  80. </agent_loop>
  81. '''
  82. QUERY_PROMPT_TEMPLATE = """现在,请通过多步思考,以客服的角色,选择合适的方法向一位用户发起问候。
  83. # 客服的基本信息
  84. {formatted_staff_profile}
  85. # 用户的信息
  86. - 微信昵称:{nickname}
  87. - 姓名:{name}
  88. - 头像:{avatar}
  89. - 偏好的称呼:{preferred_nickname}
  90. - 年龄:{age}
  91. - 地区:{region}
  92. - 健康状况:{health_conditions}
  93. - 用药信息:{medications}
  94. - 兴趣爱好:{interests}
  95. # 已知过去的对话
  96. {dialogue_history}
  97. # 当前上下文信息
  98. 时间:{current_datetime}
  99. 注意对话信息的格式为: [角色][时间]对话内容
  100. 注意分析客服和用户当前的社交阶段,先确立本次问候的目的。
  101. 注意一定要分析对话信息中的时间,避免和当前时间段不符的内容!注意一定要结合历史的对话情况进行分析和问候方式的选择!
  102. 如有必要,可以使用analyse_image分析用户头像。
  103. 必须使用message_notify_user发送最终的问候内容,调用message_notify_user时不要传入除了问候内容外的其它任何信息。
  104. 注意每次问候只使用一种话术。
  105. Now, start to process your task. Please think step by step.
  106. """
  107. class MessagePushAgent(SimpleOpenAICompatibleChatAgent):
  108. """A specialized agent for message push tasks."""
  109. def __init__(self, model: Optional[str] = VOLCENGINE_MODEL_DEEPSEEK_V3, system_prompt: Optional[str] = None,
  110. tools: Optional[List[FunctionTool]] = None,
  111. generate_cfg: Optional[dict] = None, max_run_step: Optional[int] = None):
  112. system_prompt = system_prompt or DEFAULT_SYSTEM_PROMPT
  113. tools = tools or []
  114. tools = tools.copy()
  115. tools.extend([
  116. *ImageDescriber().get_tools(),
  117. *MessageNotifier().get_tools()
  118. ])
  119. super().__init__(model, system_prompt, tools, generate_cfg, max_run_step)
  120. def generate_message(self, context: Dict, dialogue_history: List[Dict]) -> str:
  121. formatted_dialogue = MessagePushAgent.compose_dialogue(dialogue_history)
  122. query = QUERY_PROMPT_TEMPLATE.format(**context, dialogue_history=formatted_dialogue)
  123. self.run(query)
  124. for tool_call in reversed(self.tool_call_records):
  125. if tool_call['name'] == MessageNotifier.message_notify_user.__name__:
  126. return tool_call['arguments']['message']
  127. return ''
  128. @staticmethod
  129. def compose_dialogue(dialogue: List[Dict]) -> str:
  130. role_map = {'user': '用户', 'assistant': '客服'}
  131. messages = []
  132. for msg in dialogue:
  133. if not msg['content']:
  134. continue
  135. if msg['role'] not in role_map:
  136. continue
  137. format_dt = datetime.datetime.fromtimestamp(msg['timestamp'] / 1000).strftime('%Y-%m-%d %H:%M:%S')
  138. messages.append('[{}][{}]{}'.format(role_map[msg['role']], format_dt, msg['content']))
  139. return '\n'.join(messages)
  140. class DummyMessagePushAgent(MessagePushAgent):
  141. """A dummy agent for testing purposes."""
  142. def __init__(self, *args, **kwargs):
  143. super().__init__(*args, **kwargs)
  144. def generate_message(self, context: Dict, dialogue_history: List[Dict]) -> str:
  145. logger.debug(f"DummyMessagePushAgent.generate_message called, context: {context}")
  146. return "测试消息: {agent_name} -> {nickname}".format(**context)
  147. if __name__ == '__main__':
  148. import pqai_agent.logging_service
  149. pqai_agent.logging_service.setup_root_logger()
  150. agent = MessagePushAgent()
  151. test_user_profile = {
  152. 'name': '薛岱月',
  153. 'avatar': 'http://wx.qlogo.cn/mmhead/Q3auHgzwzM5glpnBtDUianJErYf9AQsptLM3N78xP3sOR8SSibsG35HQ/0',
  154. 'preferred_nickname': '月哥',
  155. 'age': 65,
  156. 'region': '北京',
  157. 'health_conditions': '高血压',
  158. 'medications': ['降压药'],
  159. 'interests': ['钓鱼', '旅游']
  160. }
  161. test_context = {
  162. "current_datetime": "2025-05-12 08:00:00",
  163. **test_user_profile
  164. }
  165. def create_ts(year, month, day, hour, minute):
  166. return datetime.datetime(year, month, day, hour, minute).timestamp() * 1000
  167. messages = [
  168. {"role": "assistant", "content": "月哥,早上好!看到您的头像是一片宁静的户外风景,感觉您一定很喜欢大自然吧?今天天气不错,您有什么计划吗?", "timestamp": create_ts(2025, 5, 10, 8, 0)},
  169. ]
  170. response = agent.generate_message(test_context, messages)
  171. print(response)