|
|
@@ -6,10 +6,12 @@ from ..config.config import Config
|
|
|
from ..schemas.chat import ChatMessage, ChatResponse, CommentRequest, CirclePromptConfig, HistoricalFigure, \
|
|
|
RephraseRequest, FigureUpsert, GroupChatRequest, PostCommentBotRequest, RoleBasedChatRequest
|
|
|
from ..db.souyue_mongo import get_mblog_by_id
|
|
|
-from ..db.mongo import save_chat_log,get_circle_prompt, upsert_circle_prompt, get_all_figures, get_figure_by_id, insert_figure, update_figure, delete_figure
|
|
|
+from ..db.mongo import save_chat_log,get_circle_prompt, upsert_circle_prompt, get_all_figures, get_figure_by_id, insert_figure, update_figure, delete_figure, \
|
|
|
+ save_group_ai_reply, get_last_group_response_id
|
|
|
from ..utils.chat_utils import get_web_search_tools, get_knowledge_search_tools
|
|
|
from ..db.ai_config import get_config_by_app_name
|
|
|
from ..db.mongo import save_chat_history, get_chat_history, delete_chat_history, get_sessions, get_last_response_id
|
|
|
+from ..db.redis_client import get_cached_group_context, cache_group_context
|
|
|
router = APIRouter()
|
|
|
|
|
|
import re
|
|
|
@@ -522,13 +524,71 @@ async def entrepreneur_chat(request: RoleBasedChatRequest):
|
|
|
client = get_client(request.app_name)
|
|
|
knowledge_id = ai_config.get("knowledgeId")
|
|
|
|
|
|
+ # 【v2.0新增】支持群聊上下文
|
|
|
+ session_id = request.session_id
|
|
|
+ group_id = request.group_id
|
|
|
+ previous_response_id = None
|
|
|
+
|
|
|
+ if session_id:
|
|
|
+ # 获取该AI的上次response_id(必须指定ai_role,防止串线)
|
|
|
+ previous_response_id = get_last_group_response_id(session_id, ai_role='entrepreneur')
|
|
|
+
|
|
|
+ # 【v2.1新增】获取群聊历史上下文(支持多模态)
|
|
|
+ context_messages = []
|
|
|
+ if group_id:
|
|
|
+ from app.db.tencent_im import im_client
|
|
|
+
|
|
|
+ try:
|
|
|
+ # 先尝试从Redis缓存读取
|
|
|
+ group_context = get_cached_group_context(group_id)
|
|
|
+
|
|
|
+ if group_context is None:
|
|
|
+ # 缓存未命中,调用腾讯云IM API
|
|
|
+ group_context = await im_client.get_group_context(group_id, max_messages=20)
|
|
|
+
|
|
|
+ # 写入缓存(2分钟TTL)
|
|
|
+ if group_context:
|
|
|
+ cache_group_context(group_id, group_context, ttl=120)
|
|
|
+
|
|
|
+ # 构造多模态消息格式
|
|
|
+ if group_context:
|
|
|
+ for msg in group_context[-20:]: # 最近20条
|
|
|
+ user_id = msg['user_id']
|
|
|
+ content_parts = []
|
|
|
+
|
|
|
+ # 添加文本内容
|
|
|
+ if msg.get('content'):
|
|
|
+ content_parts.append({"type": "input_text", "text": f"{user_id}: {msg['content']}"})
|
|
|
+
|
|
|
+ # 添加图片
|
|
|
+ for img_url in msg.get('images', []):
|
|
|
+ content_parts.append({"type": "input_image", "image_url": img_url})
|
|
|
+
|
|
|
+ # 添加视频缩略图
|
|
|
+ for video in msg.get('videos', []):
|
|
|
+ if video.get('thumb'):
|
|
|
+ content_parts.append({"type": "input_image", "image_url": video['thumb']})
|
|
|
+ content_parts.append({"type": "input_text", "text": "[视频]"})
|
|
|
+
|
|
|
+ # 添加分享标题
|
|
|
+ if msg.get('share_title'):
|
|
|
+ content_parts.append({"type": "input_text", "text": f"[分享]{msg['share_title']}"})
|
|
|
+
|
|
|
+ # 合并到上下文
|
|
|
+ if content_parts:
|
|
|
+ context_messages.extend(content_parts)
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ # 获取群历史失败不影响主流程
|
|
|
+ print(f"获取群历史失败: {e}")
|
|
|
+
|
|
|
# 企业家角色系统提示词
|
|
|
system_prompt = """
|
|
|
## 角色设定
|
|
|
|
|
|
你是一位企业家。你在群里的身份是参与者,不是主持人。
|
|
|
-背景设定:你对云悦的认知是:云悦采用“云平台+共享”模式,强调共创、共享、共赢,核心是技术共享、内容共享、用户共享、商品共享、商业价值共享。其底层逻辑是以区块链技术为核心,打造“融合不整合、跨界不打劫、独立不孤立、拥有不占有”的平台。
|
|
|
-以上背景仅作角色参考。不得声称自己就是某位现实人物,对于云悦的具体数据、客户案例、运营细节或未公开信息,若不掌握,就明确说“不确定”,不编造。
|
|
|
+背景设定:你对云悦的认知是:云悦采用"云平台+共享"模式,强调共创、共享、共赢,核心是技术共享、内容共享、用户共享、商品共享、商业价值共享。其底层逻辑是以区块链技术为核心,打造"融合不整合、跨界不打劫、独立不孤立、拥有不占有"的平台。
|
|
|
+以上背景仅作角色参考。不得声称自己就是某位现实人物,对于云悦的具体数据、客户案例、运营细节或未公开信息,若不掌握,就明确说"不确定",不编造。
|
|
|
|
|
|
## 群聊场景
|
|
|
|
|
|
@@ -539,7 +599,7 @@ async def entrepreneur_chat(request: RoleBasedChatRequest):
|
|
|
遇到以下内容保持沉默,不输出任何文字:
|
|
|
|
|
|
1. **自杀/自伤倾向**
|
|
|
- - “我不想活了””想死””想结束自己”等明确表达
|
|
|
+ - "我不想活了""想死""想结束自己"等明确表达
|
|
|
- 这超出群聊能力范围,回复反而可能延误专业救助
|
|
|
|
|
|
2. **诈骗/违规推广**
|
|
|
@@ -593,7 +653,21 @@ async def entrepreneur_chat(request: RoleBasedChatRequest):
|
|
|
|
|
|
# 构造请求消息
|
|
|
system_msg = {"role": "system", "content": [{"type": "input_text", "text": system_prompt}]}
|
|
|
- api_messages = [system_msg, {"role": "user", "content": request.message}]
|
|
|
+
|
|
|
+ # 如果有群聊上下文,添加到消息列表
|
|
|
+ user_content = [{"type": "input_text", "text": request.message}]
|
|
|
+
|
|
|
+ if context_messages:
|
|
|
+ # 添加上下文标记
|
|
|
+ context_header = [{"type": "input_text", "text": "【群聊上下文(最近20条消息)】"}]
|
|
|
+ context_footer = [{"type": "input_text", "text": f"\n\n当前用户发送的消息是:"}]
|
|
|
+
|
|
|
+ api_messages = [
|
|
|
+ system_msg,
|
|
|
+ {"role": "user", "content": context_header + context_messages + context_footer + user_content}
|
|
|
+ ]
|
|
|
+ else:
|
|
|
+ api_messages = [system_msg, {"role": "user", "content": user_content}]
|
|
|
|
|
|
# 配置工具:联网搜索 + 知识库
|
|
|
tools = get_web_search_tools()
|
|
|
@@ -605,7 +679,8 @@ async def entrepreneur_chat(request: RoleBasedChatRequest):
|
|
|
model=Config.MODEL_NAME,
|
|
|
input=api_messages,
|
|
|
stream=False,
|
|
|
- store=False,
|
|
|
+ store=True if session_id else False, # 【v2.0】有session才存储
|
|
|
+ previous_response_id=previous_response_id, # 【v2.0】延续上下文
|
|
|
text={"format": {"type": "text"}},
|
|
|
tools=tools,
|
|
|
)
|
|
|
@@ -623,9 +698,23 @@ async def entrepreneur_chat(request: RoleBasedChatRequest):
|
|
|
|
|
|
result = _strip_markdown(result)
|
|
|
|
|
|
+ # 【v2.0新增】保存群聊AI回复
|
|
|
+ if session_id and group_id and result:
|
|
|
+ save_group_ai_reply(
|
|
|
+ session_id=session_id,
|
|
|
+ group_id=group_id,
|
|
|
+ ai_role='entrepreneur',
|
|
|
+ content=result,
|
|
|
+ response_id=response.id,
|
|
|
+ trigger_message=request.message,
|
|
|
+ trigger_user_id=request.user_id,
|
|
|
+ timestamp=datetime.now(),
|
|
|
+ app_name=request.app_name,
|
|
|
+ )
|
|
|
+
|
|
|
if not result:
|
|
|
save_chat_log(
|
|
|
- user_id=request.user_id or "anonymous",
|
|
|
+ user_id=request.user_id,
|
|
|
question=request.message,
|
|
|
stream_mode=False,
|
|
|
raw_response=repr(response),
|
|
|
@@ -635,7 +724,7 @@ async def entrepreneur_chat(request: RoleBasedChatRequest):
|
|
|
|
|
|
# 记录日志
|
|
|
save_chat_log(
|
|
|
- user_id=request.user_id or "anonymous",
|
|
|
+ user_id=request.user_id,
|
|
|
question=request.message,
|
|
|
stream_mode=False,
|
|
|
raw_response=repr(response),
|
|
|
@@ -667,36 +756,91 @@ async def femininity_chat(request: RoleBasedChatRequest):
|
|
|
client = get_client(request.app_name)
|
|
|
knowledge_id = ai_config.get("knowledgeId")
|
|
|
|
|
|
+ # 【v2.0新增】支持群聊上下文
|
|
|
+ session_id = request.session_id
|
|
|
+ group_id = request.group_id
|
|
|
+ previous_response_id = None
|
|
|
+
|
|
|
+ if session_id:
|
|
|
+ # 获取该AI的上次response_id(必须指定ai_role,防止串线)
|
|
|
+ previous_response_id = get_last_group_response_id(session_id, ai_role='femininity')
|
|
|
+
|
|
|
+ # 【v2.1新增】获取群聊历史上下文(支持多模态)
|
|
|
+ context_messages = []
|
|
|
+ if group_id:
|
|
|
+ from app.db.tencent_im import im_client
|
|
|
+
|
|
|
+ try:
|
|
|
+ # 先尝试从Redis缓存读取
|
|
|
+ group_context = get_cached_group_context(group_id)
|
|
|
+
|
|
|
+ if group_context is None:
|
|
|
+ # 缓存未命中,调用腾讯云IM API
|
|
|
+ group_context = await im_client.get_group_context(group_id, max_messages=20)
|
|
|
+
|
|
|
+ # 写入缓存(2分钟TTL)
|
|
|
+ if group_context:
|
|
|
+ cache_group_context(group_id, group_context, ttl=120)
|
|
|
+
|
|
|
+ # 构造多模态消息格式
|
|
|
+ if group_context:
|
|
|
+ for msg in group_context[-20:]: # 最近20条
|
|
|
+ user_id = msg['user_id']
|
|
|
+ content_parts = []
|
|
|
+
|
|
|
+ # 添加文本内容
|
|
|
+ if msg.get('content'):
|
|
|
+ content_parts.append({"type": "input_text", "text": f"{user_id}: {msg['content']}"})
|
|
|
+
|
|
|
+ # 添加图片
|
|
|
+ for img_url in msg.get('images', []):
|
|
|
+ content_parts.append({"type": "input_image", "image_url": img_url})
|
|
|
+
|
|
|
+ # 添加视频缩略图
|
|
|
+ for video in msg.get('videos', []):
|
|
|
+ if video.get('thumb'):
|
|
|
+ content_parts.append({"type": "input_image", "image_url": video['thumb']})
|
|
|
+ content_parts.append({"type": "input_text", "text": "[视频]"})
|
|
|
+
|
|
|
+ # 添加分享标题
|
|
|
+ if msg.get('share_title'):
|
|
|
+ content_parts.append({"type": "input_text", "text": f"[分享]{msg['share_title']}"})
|
|
|
+
|
|
|
+ # 合并到上下文
|
|
|
+ if content_parts:
|
|
|
+ context_messages.extend(content_parts)
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ # 获取群历史失败不影响主流程
|
|
|
+ print(f"获取群历史失败: {e}")
|
|
|
+
|
|
|
# 女性情感角色系统提示词
|
|
|
system_prompt = """
|
|
|
## 角色设定
|
|
|
|
|
|
你是一名女性,你在群里的身份是参与者,不是主持人。
|
|
|
-背景设定:你对云悦的认知是:云悦采用“云平台+共享”模式,强调共创、共享、共赢,核心是技术共享、内容共享、用户共享、商品共享、商业价值共享。其底层逻辑是以区块链技术为核心,打造“融合不整合、跨界不打劫、独立不孤立、拥有不占有”的平台。
|
|
|
-以上背景仅作角色参考。不得声称自己就是某位现实人物,对于云悦的具体数据、客户案例、运营细节或未公开信息,若不掌握,就明确说“不确定”,不编造。
|
|
|
+背景设定:你对云悦的认知是:云悦采用"云平台+共享"模式,强调共创、共享、共赢,核心是技术共享、内容共享、用户共享、商品共享、商业价值共享。其底层逻辑是以区块链技术为核心,打造"融合不整合、跨界不打劫、独立不孤立、拥有不占有"的平台。
|
|
|
+以上背景仅作角色参考。不得声称自己就是某位现实人物,对于云悦的具体数据、客户案例、运营细节或未公开信息,若不掌握,就明确说"不确定",不编造。
|
|
|
|
|
|
## 群聊场景
|
|
|
|
|
|
-群成员以女性为主,年龄约20—40岁,讨论情感关系、女性成长、心理状态、营养健康、工作压力和生活困惑,也可能讨论涉及经营、管理、市场、产品、技术、融资及创业经历。。
|
|
|
+群成员以女性为主,年龄约20—40岁,讨论情感关系、女性成长、心理状态、营养健康、工作压力和生活困惑,也可能讨论涉及经营、管理、市场、产品、技术、融资及创业经历。
|
|
|
|
|
|
## 不回复的情况(最高优先级)
|
|
|
|
|
|
遇到以下内容保持沉默,不输出任何文字:
|
|
|
|
|
|
-1. **自杀/自伤/极端情绪**
|
|
|
- - 明确表达不想活、想死、想结束自己或自残等意图
|
|
|
-
|
|
|
-2. **亲密关系问题**
|
|
|
- - 男朋友、老公、伴侣、恋爱、分手、婚姻等相关困惑
|
|
|
-
|
|
|
-3. **诈骗/违规推广**
|
|
|
- - 承诺高收益、保本翻倍、拉人入群、传销话术、资金盘或未经允许的广告
|
|
|
+1. **自杀/自伤倾向**
|
|
|
+ - "我不想活了""想死""想结束自己"等明确表达
|
|
|
+ - 这超出群聊能力范围,回复反而可能延误专业救助
|
|
|
|
|
|
-4. **极端内容**
|
|
|
- - 煽动仇恨、极端政治、宗教争议、暴力恐怖等内容
|
|
|
+2. **诈骗/违规推广**
|
|
|
+ - 承诺高收益、保本翻倍、拉人入群
|
|
|
+ - 传销话术、资金盘、未经允许的广告
|
|
|
|
|
|
-5. **低价值消息**
|
|
|
- - 明显的机器人测试、灌水刷屏或没有实际内容的消息
|
|
|
+3. **极端内容**
|
|
|
+ - 煽动仇恨、极端政治、宗教争议
|
|
|
+ - 明显的机器人测试、灌水刷屏
|
|
|
|
|
|
遇到以上内容直接保持沉默,不解释为什么不回复。
|
|
|
|
|
|
@@ -718,15 +862,15 @@ async def femininity_chat(request: RoleBasedChatRequest):
|
|
|
|
|
|
1. 不是每条消息都要回复,只在被@、被提问、话题值得补充或群聊冷场、无人回应时发言。
|
|
|
2. 像真实群友一样说话,使用自然口语,不像客服、教材或鸡汤号。
|
|
|
-3. 不使用固定的“共情—分析—建议”模板,不用空泛安慰、夸张赞美、亲昵称呼或性别刻板印象;不主动抛出新话题。
|
|
|
-4. 简单问题简短回答,不堆叠感叹号,不使用“首先、其次、最后”等机械分条表达。
|
|
|
+3. 不使用固定的"共情—分析—建议"模板,不用空泛安慰、夸张赞美、亲昵称呼或性别刻板印象;不主动抛出新话题。
|
|
|
+4. 简单问题简短回答,不堆叠感叹号,不使用"首先、其次、最后"等机械分条表达。
|
|
|
|
|
|
## 专业边界
|
|
|
|
|
|
- 心理学内容用于帮助理解,不进行远程诊断;持续失眠、抑郁、惊恐或进食问题等情况建议寻求线下专业帮助。
|
|
|
- 女性健康和营养问题不作医疗保证;涉及症状、用药、孕期、哺乳、经期异常或慢性病时,提醒咨询医生或营养专业人士。
|
|
|
- 企业、技术和项目问题给有条件的建议;法律、税务、投资、用工等高风险问题提醒咨询专业人士。
|
|
|
-- 保护个人隐私和商业机密,不编造云悦数据、客户案例、项目经历或实时资讯;不确定的信息明确说明“不确定”。
|
|
|
+- 保护个人隐私和商业机密,不编造云悦数据、客户案例、项目经历或实时资讯;不确定的信息明确说明"不确定"。
|
|
|
|
|
|
## 输出格式
|
|
|
|
|
|
@@ -737,7 +881,21 @@ async def femininity_chat(request: RoleBasedChatRequest):
|
|
|
|
|
|
# 构造请求消息
|
|
|
system_msg = {"role": "system", "content": [{"type": "input_text", "text": system_prompt}]}
|
|
|
- api_messages = [system_msg, {"role": "user", "content": request.message}]
|
|
|
+
|
|
|
+ # 如果有群聊上下文,添加到消息列表
|
|
|
+ user_content = [{"type": "input_text", "text": request.message}]
|
|
|
+
|
|
|
+ if context_messages:
|
|
|
+ # 添加上下文标记
|
|
|
+ context_header = [{"type": "input_text", "text": "【群聊上下文(最近20条消息)】"}]
|
|
|
+ context_footer = [{"type": "input_text", "text": f"\n\n当前用户发送的消息是:"}]
|
|
|
+
|
|
|
+ api_messages = [
|
|
|
+ system_msg,
|
|
|
+ {"role": "user", "content": context_header + context_messages + context_footer + user_content}
|
|
|
+ ]
|
|
|
+ else:
|
|
|
+ api_messages = [system_msg, {"role": "user", "content": user_content}]
|
|
|
|
|
|
# 配置工具:联网搜索 + 知识库
|
|
|
tools = get_web_search_tools()
|
|
|
@@ -749,7 +907,8 @@ async def femininity_chat(request: RoleBasedChatRequest):
|
|
|
model="doubao-seed-2-1-lite-260915",
|
|
|
input=api_messages,
|
|
|
stream=False,
|
|
|
- store=False,
|
|
|
+ store=True if session_id else False, # 【v2.0】有session才存储
|
|
|
+ previous_response_id=previous_response_id, # 【v2.0】延续上下文
|
|
|
text={"format": {"type": "text"}},
|
|
|
tools=tools,
|
|
|
)
|
|
|
@@ -767,9 +926,23 @@ async def femininity_chat(request: RoleBasedChatRequest):
|
|
|
|
|
|
result = _strip_markdown(result)
|
|
|
|
|
|
+ # 【v2.0新增】保存群聊AI回复
|
|
|
+ if session_id and group_id and result:
|
|
|
+ save_group_ai_reply(
|
|
|
+ session_id=session_id,
|
|
|
+ group_id=group_id,
|
|
|
+ ai_role='femininity',
|
|
|
+ content=result,
|
|
|
+ response_id=response.id,
|
|
|
+ trigger_message=request.message,
|
|
|
+ trigger_user_id=request.user_id,
|
|
|
+ timestamp=datetime.now(),
|
|
|
+ app_name=request.app_name,
|
|
|
+ )
|
|
|
+
|
|
|
if not result:
|
|
|
save_chat_log(
|
|
|
- user_id=request.user_id or "anonymous",
|
|
|
+ user_id=request.user_id,
|
|
|
question=request.message,
|
|
|
stream_mode=False,
|
|
|
raw_response=repr(response),
|
|
|
@@ -779,7 +952,7 @@ async def femininity_chat(request: RoleBasedChatRequest):
|
|
|
|
|
|
# 记录日志
|
|
|
save_chat_log(
|
|
|
- user_id=request.user_id or "anonymous",
|
|
|
+ user_id=request.user_id,
|
|
|
question=request.message,
|
|
|
stream_mode=False,
|
|
|
raw_response=repr(response),
|