|
@@ -1140,3 +1140,457 @@ async def femininity_chat(request: RoleBasedChatRequest):
|
|
|
raise
|
|
raise
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
raise HTTPException(status_code=500, detail=f"Femininity chat error: {str(e)}")
|
|
raise HTTPException(status_code=500, detail=f"Femininity chat error: {str(e)}")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# AI社群角色对话
|
|
|
|
|
+@router.post("/aiCommunity")
|
|
|
|
|
+async def ai_community_chat(request: RoleBasedChatRequest):
|
|
|
|
|
+ """AI氛围社群角色对话接口"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ if _should_block_role_message(request.message):
|
|
|
|
|
+ raise HTTPException(status_code=500, detail="AI未能生成回复")
|
|
|
|
|
+
|
|
|
|
|
+ ai_config = get_config_by_app_name(request.app_name)
|
|
|
|
|
+ if not ai_config:
|
|
|
|
|
+ raise HTTPException(status_code=404, detail=f"未找到appName '{request.app_name}' 的配置")
|
|
|
|
|
+
|
|
|
|
|
+ client = get_client(request.app_name)
|
|
|
|
|
+ knowledge_id = ai_config.get("knowledgeId")
|
|
|
|
|
+
|
|
|
|
|
+ session_id = request.session_id
|
|
|
|
|
+ group_id = request.group_id
|
|
|
|
|
+ previous_response_id = None
|
|
|
|
|
+
|
|
|
|
|
+ if session_id:
|
|
|
|
|
+ previous_response_id = get_last_group_response_id(session_id, ai_role='ai_community')
|
|
|
|
|
+
|
|
|
|
|
+ context_messages = []
|
|
|
|
|
+ if group_id:
|
|
|
|
|
+ from app.db.tencent_im import im_client
|
|
|
|
|
+ try:
|
|
|
|
|
+ group_context = get_cached_group_context(group_id)
|
|
|
|
|
+ if group_context is None:
|
|
|
|
|
+ group_context = await im_client.get_group_context(group_id, max_messages=20)
|
|
|
|
|
+ if group_context:
|
|
|
|
|
+ cache_group_context(group_id, group_context, ttl=120)
|
|
|
|
|
+ if group_context:
|
|
|
|
|
+ for msg in group_context[-20:]:
|
|
|
|
|
+ msg_user_id = msg['user_id']
|
|
|
|
|
+ content_parts = []
|
|
|
|
|
+ if msg.get('content'):
|
|
|
|
|
+ content_parts.append({"type": "input_text", "text": f"{msg_user_id}: {msg['content']}"})
|
|
|
|
|
+ update_group_mood(group_id, msg_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}")
|
|
|
|
|
+
|
|
|
|
|
+ # AI社群角色系统提示词
|
|
|
|
|
+ system_prompt = """
|
|
|
|
|
+## 角色设定
|
|
|
|
|
+
|
|
|
|
|
+你是一名对人工智能领域有浓厚兴趣的群友,经常关注大模型、AI工具和AI行业动向。你在群里的身份是参与者,不是主持人或讲师。
|
|
|
|
|
+对于你不掌握的具体数据、最新消息或未经核实的信息,明确说"不确定"或"我没看到最新的",不编造。
|
|
|
|
|
+
|
|
|
|
|
+## 群聊场景
|
|
|
|
|
+
|
|
|
|
|
+群成员以AI爱好者、开发者、创业者和科技从业者为主,日常讨论大模型进展、AI工具使用、马斯克动态、AI创业机会、提示词技巧等话题,偶尔也聊AI行业的争议和担忧。
|
|
|
|
|
+
|
|
|
|
|
+## 不回复的情况(最高优先级)
|
|
|
|
|
+
|
|
|
|
|
+遇到以下内容保持沉默,不输出任何文字:
|
|
|
|
|
+
|
|
|
|
|
+1. **自杀/自伤倾向**
|
|
|
|
|
+2. **诈骗/违规推广**:承诺高收益、保本翻倍、拉人入群、传销话术
|
|
|
|
|
+3. **极端内容**:煽动仇恨、极端政治、宗教争议、明显机器人测试
|
|
|
|
|
+4. **与AI/科技完全无关的话题**(情感困惑、家庭纠纷、医疗问题等):不强行回复,保持沉默
|
|
|
|
|
+
|
|
|
|
|
+遇到以上内容直接保持沉默,不解释为什么不回复。
|
|
|
|
|
+
|
|
|
|
|
+## 回应原则
|
|
|
|
|
+
|
|
|
|
|
+**技术/产品问题:** 给出你自己的判断,可以说"我觉得""我用下来感觉",不要总用"业界认为"或"普遍观点"。
|
|
|
|
|
+
|
|
|
|
|
+**行业动态讨论:** 有自己的看法,可以对某个方向表达支持或质疑,不是中立播报员。
|
|
|
|
|
+
|
|
|
|
|
+**争议话题(AGI时间线、AI取代人、AI安全):** 给出你认为站得住脚的角度,不回避,但也不下绝对结论。
|
|
|
|
|
+
|
|
|
|
|
+**闲聊:** 不必强行回复,尤其是与AI话题完全无关时。
|
|
|
|
|
+
|
|
|
|
|
+## 互动风格
|
|
|
|
|
+
|
|
|
|
|
+1. 只在被@、被提问、话题值得补充或群聊冷场时发言,不是每条消息都要回。
|
|
|
|
|
+2. 说话像群友,不像科技媒体:用"这个模型"而不是"该模型",用"感觉"而不是"据悉"。
|
|
|
|
|
+3. 对马斯克、OpenAI、Anthropic、谷歌、字节、阿里等公司动态可以有态度,但不散布未经证实的消息。
|
|
|
|
|
+4. 简单问题简短回答,不要分一二三四。
|
|
|
|
|
+
|
|
|
|
|
+## 专业边界
|
|
|
|
|
+
|
|
|
|
|
+- AI技术问题给有条件的判断,不把你的理解说成权威结论。
|
|
|
|
|
+- 涉及投资、法律、安全漏洞等敏感问题,建议咨询专业人士,不做保证性判断。
|
|
|
|
|
+- 不声称自己是某位真实人物,不编造最新发布会或论文数据。
|
|
|
|
|
+
|
|
|
|
|
+## 输出格式
|
|
|
|
|
+
|
|
|
|
|
+只输出可以直接发到群里的消息,不加解释、分析过程、角色标签或引号。
|
|
|
|
|
+一次只发一条消息。
|
|
|
|
|
+如果不需要回应,保持沉默(不输出任何内容)。
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+ # 【v3.0】注入用户长期记忆
|
|
|
|
|
+ mem = get_user_memory(request.user_id, group_id, "ai_community")
|
|
|
|
|
+ if mem and mem.get("facts"):
|
|
|
|
|
+ fact_lines = [
|
|
|
|
|
+ f"- {f['key']}: {f['value']}"
|
|
|
|
|
+ for f in mem["facts"]
|
|
|
|
|
+ if isinstance(f, dict) and f.get("key") and f.get("value")
|
|
|
|
|
+ ]
|
|
|
|
|
+ if fact_lines:
|
|
|
|
|
+ system_prompt += (
|
|
|
|
|
+ "\n\n## 你对当前发言用户的已知背景\n"
|
|
|
|
|
+ + "\n".join(fact_lines)
|
|
|
|
|
+ + "\n参照这些背景给出更具体的回应,但不要生硬复述。"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 【v3.0】注入群情绪状态
|
|
|
|
|
+ system_prompt += build_mood_block(group_id)
|
|
|
|
|
+
|
|
|
|
|
+ system_msg = {"role": "system", "content": [{"type": "input_text", "text": system_prompt}]}
|
|
|
|
|
+ user_content = [{"type": "input_text", "text": request.message}]
|
|
|
|
|
+
|
|
|
|
|
+ if context_messages:
|
|
|
|
|
+ context_header = [{"type": "input_text", "text": "【群聊上下文(最近20条消息)】"}]
|
|
|
|
|
+ context_footer = [{"type": "input_text", "text": "\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 = []
|
|
|
|
|
+ if knowledge_id:
|
|
|
|
|
+ try:
|
|
|
|
|
+ kb_results = await search_knowledge(resource_id=knowledge_id, query=request.message, limit=5)
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"知识库预检失败,降级到联网搜索: {e}")
|
|
|
|
|
+ kb_results = []
|
|
|
|
|
+ else:
|
|
|
|
|
+ kb_results = []
|
|
|
|
|
+
|
|
|
|
|
+ if kb_results:
|
|
|
|
|
+ kb_texts = [r.content for r in kb_results if r.content]
|
|
|
|
|
+ if kb_texts:
|
|
|
|
|
+ kb_context_block = [{"type": "input_text", "text": "【知识库参考资料】\n" + "\n\n".join(kb_texts) + "\n\n请优先基于以上资料回答,资料中没有的内容不要编造。"}]
|
|
|
|
|
+ if context_messages:
|
|
|
|
|
+ api_messages[-1]["content"] = kb_context_block + api_messages[-1]["content"]
|
|
|
|
|
+ else:
|
|
|
|
|
+ api_messages[-1]["content"] = kb_context_block + user_content
|
|
|
|
|
+ else:
|
|
|
|
|
+ tools = get_web_search_tools()
|
|
|
|
|
+
|
|
|
|
|
+ response = client.responses.create(
|
|
|
|
|
+ model="doubao-seed-2-1-lite-260915",
|
|
|
|
|
+ input=api_messages,
|
|
|
|
|
+ stream=False,
|
|
|
|
|
+ store=True if session_id else False,
|
|
|
|
|
+ previous_response_id=previous_response_id,
|
|
|
|
|
+ text={"format": {"type": "text"}},
|
|
|
|
|
+ tools=tools,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ result = ""
|
|
|
|
|
+ for item in response.output:
|
|
|
|
|
+ if hasattr(item, 'type') and item.type == 'message' and hasattr(item, 'content'):
|
|
|
|
|
+ if isinstance(item.content, list):
|
|
|
|
|
+ for content_item in item.content:
|
|
|
|
|
+ if hasattr(content_item, 'text'):
|
|
|
|
|
+ result += content_item.text
|
|
|
|
|
+ else:
|
|
|
|
|
+ result += str(item.content)
|
|
|
|
|
+
|
|
|
|
|
+ result = _strip_markdown(result)
|
|
|
|
|
+
|
|
|
|
|
+ if session_id and group_id and result:
|
|
|
|
|
+ save_group_ai_reply(
|
|
|
|
|
+ session_id=session_id,
|
|
|
|
|
+ group_id=group_id,
|
|
|
|
|
+ ai_role='ai_community',
|
|
|
|
|
+ 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 result:
|
|
|
|
|
+ asyncio.create_task(_extract_memory(
|
|
|
|
|
+ user_id=request.user_id,
|
|
|
|
|
+ group_id=group_id,
|
|
|
|
|
+ ai_role="ai_community",
|
|
|
|
|
+ trigger_msg=request.message,
|
|
|
|
|
+ ai_reply=result,
|
|
|
|
|
+ app_name=request.app_name,
|
|
|
|
|
+ ))
|
|
|
|
|
+
|
|
|
|
|
+ if not result:
|
|
|
|
|
+ save_chat_log(
|
|
|
|
|
+ user_id=request.user_id,
|
|
|
|
|
+ question=request.message,
|
|
|
|
|
+ stream_mode=False,
|
|
|
|
|
+ raw_response=repr(response),
|
|
|
|
|
+ status="error",
|
|
|
|
|
+ )
|
|
|
|
|
+ raise HTTPException(status_code=500, detail="AI未能生成回复")
|
|
|
|
|
+
|
|
|
|
|
+ save_chat_log(
|
|
|
|
|
+ user_id=request.user_id,
|
|
|
|
|
+ question=request.message,
|
|
|
|
|
+ stream_mode=False,
|
|
|
|
|
+ raw_response=repr(response),
|
|
|
|
|
+ status="success",
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return {"data": result}
|
|
|
|
|
+
|
|
|
|
|
+ except HTTPException:
|
|
|
|
|
+ raise
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ raise HTTPException(status_code=500, detail=f"AI Community chat error: {str(e)}")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# 掼蛋角色对话
|
|
|
|
|
+@router.post("/guandan")
|
|
|
|
|
+async def guandan_chat(request: RoleBasedChatRequest):
|
|
|
|
|
+ """掼蛋社群角色对话接口"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ if _should_block_role_message(request.message):
|
|
|
|
|
+ raise HTTPException(status_code=500, detail="AI未能生成回复")
|
|
|
|
|
+
|
|
|
|
|
+ ai_config = get_config_by_app_name(request.app_name)
|
|
|
|
|
+ if not ai_config:
|
|
|
|
|
+ raise HTTPException(status_code=404, detail=f"未找到appName '{request.app_name}' 的配置")
|
|
|
|
|
+
|
|
|
|
|
+ client = get_client(request.app_name)
|
|
|
|
|
+ knowledge_id = ai_config.get("knowledgeId")
|
|
|
|
|
+
|
|
|
|
|
+ session_id = request.session_id
|
|
|
|
|
+ group_id = request.group_id
|
|
|
|
|
+ previous_response_id = None
|
|
|
|
|
+
|
|
|
|
|
+ if session_id:
|
|
|
|
|
+ previous_response_id = get_last_group_response_id(session_id, ai_role='guandan')
|
|
|
|
|
+
|
|
|
|
|
+ context_messages = []
|
|
|
|
|
+ if group_id:
|
|
|
|
|
+ from app.db.tencent_im import im_client
|
|
|
|
|
+ try:
|
|
|
|
|
+ group_context = get_cached_group_context(group_id)
|
|
|
|
|
+ if group_context is None:
|
|
|
|
|
+ group_context = await im_client.get_group_context(group_id, max_messages=20)
|
|
|
|
|
+ if group_context:
|
|
|
|
|
+ cache_group_context(group_id, group_context, ttl=120)
|
|
|
|
|
+
|
|
|
|
|
+ if group_context:
|
|
|
|
|
+ for msg in group_context[-20:]:
|
|
|
|
|
+ msg_user_id = msg['user_id']
|
|
|
|
|
+ content_parts = []
|
|
|
|
|
+ if msg.get('content'):
|
|
|
|
|
+ content_parts.append({"type": "input_text", "text": f"{msg_user_id}: {msg['content']}"})
|
|
|
|
|
+ update_group_mood(group_id, msg_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 = """
|
|
|
|
|
+## 角色设定
|
|
|
|
|
+
|
|
|
|
|
+你是一名掼蛋老手,对掼蛋规则、打法和赛事都很熟悉。你在群里的身份是参与者,不是裁判或教练。
|
|
|
|
|
+对于具体赛事结果、职业选手信息或你不确定的内容,明确说"不太清楚",不编造。
|
|
|
|
|
+
|
|
|
|
|
+## 群聊场景
|
|
|
|
|
+
|
|
|
|
|
+群成员都是掼蛋爱好者,讨论掼蛋比赛动态、打牌技巧、常见牌型处理、组队配合、贡牌策略等话题,也会分享自己打牌的经历和感受。
|
|
|
|
|
+
|
|
|
|
|
+## 不回复的情况(最高优先级)
|
|
|
|
|
+
|
|
|
|
|
+遇到以下内容保持沉默,不输出任何文字:
|
|
|
|
|
+
|
|
|
|
|
+1. **自杀/自伤倾向**
|
|
|
|
|
+2. **诈骗/违规推广**:承诺高收益、拉人入群、传销话术、赌博套利相关
|
|
|
|
|
+3. **极端内容**:煽动仇恨、极端政治、宗教争议
|
|
|
|
|
+4. **与掼蛋完全无关的话题**(情感困惑、工作问题、医疗健康等):不强行回复
|
|
|
|
|
+
|
|
|
|
|
+遇到以上内容直接保持沉默,不解释为什么不回复。
|
|
|
|
|
+
|
|
|
|
|
+## 回应原则
|
|
|
|
|
+
|
|
|
|
|
+**技巧/打法问题:** 给出具体的操作建议,说"我遇到这情况一般会...",不要泛泛而谈。
|
|
|
|
|
+
|
|
|
|
|
+**赛事讨论:** 对比赛结果或精彩牌局可以有自己的看法,不是简单陈述。
|
|
|
|
|
+
|
|
|
|
|
+**吐槽/经历分享:** 先共情,可以说"这局太难了",再问问具体情况。
|
|
|
|
|
+
|
|
|
|
|
+**规则疑问:** 直接说清楚,不绕弯子。
|
|
|
|
|
+
|
|
|
|
|
+**闲聊:** 与掼蛋无关时可以不回。
|
|
|
|
|
+
|
|
|
|
|
+## 互动风格
|
|
|
|
|
+
|
|
|
|
|
+1. 只在被@、被提问、话题值得补充或群聊冷场时发言。
|
|
|
|
|
+2. 像群里的牌友一样说话,口语化,不像规则手册。
|
|
|
|
|
+3. 可以用掼蛋常见口语,比如"打主""跑高""抓贡""炸弹""天王炸""进贡""还贡""贡牌""上贡"等。
|
|
|
|
|
+4. 简单问题简短回答,不分条列点。
|
|
|
|
|
+
|
|
|
|
|
+## 专业边界
|
|
|
|
|
+
|
|
|
|
|
+- 涉及金钱赌博、违规活动相关话题不参与,也不给建议。
|
|
|
|
|
+- 不编造职业赛事结果或选手信息。
|
|
|
|
|
+
|
|
|
|
|
+## 输出格式
|
|
|
|
|
+
|
|
|
|
|
+只输出可以直接发到群里的消息,不加解释、分析过程、角色标签或引号。
|
|
|
|
|
+一次只发一条消息。
|
|
|
|
|
+如果不需要回应,保持沉默(不输出任何内容)。
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+ # 【v3.0】注入用户长期记忆
|
|
|
|
|
+ mem = get_user_memory(request.user_id, group_id, "guandan")
|
|
|
|
|
+ if mem and mem.get("facts"):
|
|
|
|
|
+ fact_lines = [
|
|
|
|
|
+ f"- {f['key']}: {f['value']}"
|
|
|
|
|
+ for f in mem["facts"]
|
|
|
|
|
+ if isinstance(f, dict) and f.get("key") and f.get("value")
|
|
|
|
|
+ ]
|
|
|
|
|
+ if fact_lines:
|
|
|
|
|
+ system_prompt += (
|
|
|
|
|
+ "\n\n## 你对当前发言用户的已知背景\n"
|
|
|
|
|
+ + "\n".join(fact_lines)
|
|
|
|
|
+ + "\n参照这些背景给出更贴近实际情况的回应,但不要生硬复述。"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 【v3.0】注入群情绪状态
|
|
|
|
|
+ system_prompt += build_mood_block(group_id)
|
|
|
|
|
+
|
|
|
|
|
+ system_msg = {"role": "system", "content": [{"type": "input_text", "text": system_prompt}]}
|
|
|
|
|
+ user_content = [{"type": "input_text", "text": request.message}]
|
|
|
|
|
+
|
|
|
|
|
+ if context_messages:
|
|
|
|
|
+ context_header = [{"type": "input_text", "text": "【群聊上下文(最近20条消息)】"}]
|
|
|
|
|
+ context_footer = [{"type": "input_text", "text": "\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 = []
|
|
|
|
|
+ if knowledge_id:
|
|
|
|
|
+ try:
|
|
|
|
|
+ kb_results = await search_knowledge(resource_id=knowledge_id, query=request.message, limit=5)
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"知识库预检失败,降级到联网搜索: {e}")
|
|
|
|
|
+ kb_results = []
|
|
|
|
|
+ else:
|
|
|
|
|
+ kb_results = []
|
|
|
|
|
+
|
|
|
|
|
+ if kb_results:
|
|
|
|
|
+ kb_texts = [r.content for r in kb_results if r.content]
|
|
|
|
|
+ if kb_texts:
|
|
|
|
|
+ kb_context_block = [{"type": "input_text", "text": "【知识库参考资料】\n" + "\n\n".join(kb_texts) + "\n\n请优先基于以上资料回答,资料中没有的内容不要编造。"}]
|
|
|
|
|
+ if context_messages:
|
|
|
|
|
+ api_messages[-1]["content"] = kb_context_block + api_messages[-1]["content"]
|
|
|
|
|
+ else:
|
|
|
|
|
+ api_messages[-1]["content"] = kb_context_block + user_content
|
|
|
|
|
+ else:
|
|
|
|
|
+ tools = get_web_search_tools()
|
|
|
|
|
+
|
|
|
|
|
+ response = client.responses.create(
|
|
|
|
|
+ model="doubao-seed-2-1-lite-260915",
|
|
|
|
|
+ input=api_messages,
|
|
|
|
|
+ stream=False,
|
|
|
|
|
+ store=True if session_id else False,
|
|
|
|
|
+ previous_response_id=previous_response_id,
|
|
|
|
|
+ text={"format": {"type": "text"}},
|
|
|
|
|
+ tools=tools,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ result = ""
|
|
|
|
|
+ for item in response.output:
|
|
|
|
|
+ if hasattr(item, 'type') and item.type == 'message' and hasattr(item, 'content'):
|
|
|
|
|
+ if isinstance(item.content, list):
|
|
|
|
|
+ for content_item in item.content:
|
|
|
|
|
+ if hasattr(content_item, 'text'):
|
|
|
|
|
+ result += content_item.text
|
|
|
|
|
+ else:
|
|
|
|
|
+ result += str(item.content)
|
|
|
|
|
+
|
|
|
|
|
+ result = _strip_markdown(result)
|
|
|
|
|
+
|
|
|
|
|
+ if session_id and group_id and result:
|
|
|
|
|
+ save_group_ai_reply(
|
|
|
|
|
+ session_id=session_id,
|
|
|
|
|
+ group_id=group_id,
|
|
|
|
|
+ ai_role='guandan',
|
|
|
|
|
+ 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 result:
|
|
|
|
|
+ asyncio.create_task(_extract_memory(
|
|
|
|
|
+ user_id=request.user_id,
|
|
|
|
|
+ group_id=group_id,
|
|
|
|
|
+ ai_role="guandan",
|
|
|
|
|
+ trigger_msg=request.message,
|
|
|
|
|
+ ai_reply=result,
|
|
|
|
|
+ app_name=request.app_name,
|
|
|
|
|
+ ))
|
|
|
|
|
+
|
|
|
|
|
+ if not result:
|
|
|
|
|
+ save_chat_log(
|
|
|
|
|
+ user_id=request.user_id,
|
|
|
|
|
+ question=request.message,
|
|
|
|
|
+ stream_mode=False,
|
|
|
|
|
+ raw_response=repr(response),
|
|
|
|
|
+ status="error",
|
|
|
|
|
+ )
|
|
|
|
|
+ raise HTTPException(status_code=500, detail="AI未能生成回复")
|
|
|
|
|
+
|
|
|
|
|
+ save_chat_log(
|
|
|
|
|
+ user_id=request.user_id,
|
|
|
|
|
+ question=request.message,
|
|
|
|
|
+ stream_mode=False,
|
|
|
|
|
+ raw_response=repr(response),
|
|
|
|
|
+ status="success",
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return {"data": result}
|
|
|
|
|
+
|
|
|
|
|
+ except HTTPException:
|
|
|
|
|
+ raise
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ raise HTTPException(status_code=500, detail=f"Guandan chat error: {str(e)}")
|