chat_tools.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. from fastapi import APIRouter, HTTPException
  2. from datetime import datetime
  3. from ..core.ark_client import get_client
  4. from ..config.config import Config
  5. from ..schemas.chat import ChatMessage, ChatResponse, CommentRequest, CirclePromptConfig, HistoricalFigure, RephraseRequest, FigureUpsert
  6. from ..db.souyue_mongo import get_mblog_by_id
  7. from ..db.mongo import get_circle_prompt, upsert_circle_prompt, get_all_figures, get_figure_by_id, insert_figure, update_figure, delete_figure
  8. router = APIRouter()
  9. def _build_prompt(product_text: str, prompt_config: dict) -> str:
  10. name = prompt_config.get("name", "兴趣圈")
  11. role = prompt_config.get("role", "活跃用户")
  12. style = prompt_config.get("style", "自然亲切,有活人感")
  13. keywords: list = prompt_config.get("keywords") or []
  14. forbidden: list = prompt_config.get("forbidden") or []
  15. extraInstruction = prompt_config.get("extra_instruction")
  16. lines = [
  17. f"你是{role},活跃在{name}兴趣圈。",
  18. "请根据以下帖子信息,生成一条10-30字的评论,要求:",
  19. "1. 内容指向性强,结合帖子具体内容",
  20. f"2. 风格:{style}",
  21. ]
  22. seq = 3
  23. if keywords:
  24. lines.append(f"{seq}. 适当融入关键词(自然使用):{', '.join(keywords)}")
  25. seq += 1
  26. if forbidden:
  27. lines.append(f"{seq}. 禁止使用以下词语:{', '.join(forbidden)}")
  28. seq += 1
  29. lines.append(f"{seq}. 语言自然,不要暴露你是AI")
  30. # 额外要求
  31. if extraInstruction:
  32. lines.append(f"【额外要求】{','.join(extraInstruction)}")
  33. lines.append(f"\n帖子内容:{product_text}")
  34. return "\n".join(lines)
  35. # 存储/更新兴趣圈提示词模版(appName 已存在则覆盖)
  36. @router.post("/prompt")
  37. async def save_circle_prompt(promptcfg: CirclePromptConfig):
  38. try:
  39. upsert_circle_prompt(promptcfg.model_dump())
  40. return {"message": "保存成功", "appName": promptcfg.appName}
  41. except Exception as e:
  42. raise HTTPException(status_code=500, detail=f"保存失败: {str(e)}")
  43. # 评论帖子的马甲机器人,无状态,支持批量对多个帖子智能回复
  44. @router.post("/batchPostCommentBot", response_model=ChatResponse)
  45. async def generate_post_comment(request: CommentRequest):
  46. doc = get_mblog_by_id(request.id)
  47. if not doc:
  48. raise HTTPException(status_code=404, detail="帖子不存在")
  49. title = doc.get("title", "")
  50. brief = doc.get("brief", "")
  51. nickname = doc.get("nickname", "")
  52. app_name = doc.get("appName", "")
  53. images: list = doc.get("images") or []
  54. product_text = f"主题:{title}\n摘要:{brief}\n发布者:{nickname}"
  55. file_list = []
  56. if images:
  57. for img_url in images:
  58. file_list.append({"type": "input_image", "image_url": img_url})
  59. prompt_config = get_circle_prompt(app_name)
  60. input_text = _build_prompt(product_text, prompt_config)
  61. content = file_list + [{"type": "input_text", "text": input_text}]
  62. print(f"concat text: {content}")
  63. client = get_client(app_name)
  64. response = client.responses.create(
  65. model=Config.MODEL_NAME,
  66. input=[{"role": "user", "content": content}],
  67. )
  68. message_content = ""
  69. for item in response.output:
  70. if hasattr(item, 'type') and item.type == 'message' and hasattr(item, 'content'):
  71. if isinstance(item.content, list):
  72. for content_item in item.content:
  73. if hasattr(content_item, 'text'):
  74. message_content += content_item.text
  75. else:
  76. message_content += str(item.content)
  77. if not message_content:
  78. raise HTTPException(status_code=500, detail="AI未能生成评论")
  79. return ChatResponse(
  80. message=ChatMessage(role="assistant", content=message_content, timestamp=datetime.now()),
  81. model=response.model,
  82. usage=response.usage.model_dump() if response.usage else None,
  83. )
  84. # ===================== 历史人物管理 =====================
  85. # 获取历史人物列表
  86. @router.get("/figures", response_model=list[HistoricalFigure])
  87. async def list_figures():
  88. return get_all_figures()
  89. # 获取单个历史人物
  90. @router.get("/figures/{id}", response_model=HistoricalFigure)
  91. async def get_figure(id: str):
  92. doc = get_figure_by_id(id)
  93. if not doc:
  94. raise HTTPException(status_code=404, detail="历史人物不存在")
  95. return doc
  96. # 新增历史人物
  97. @router.post("/figures", response_model=HistoricalFigure)
  98. async def create_figure(figure: FigureUpsert):
  99. inserted_id = insert_figure(figure.model_dump())
  100. if not inserted_id:
  101. raise HTTPException(status_code=500, detail="新增失败")
  102. return {**figure.model_dump(), "_id": inserted_id}
  103. # 修改历史人物
  104. @router.put("/figures/{id}", response_model=HistoricalFigure)
  105. async def modify_figure(id: str, figure: FigureUpsert):
  106. matched = update_figure(id, figure.model_dump())
  107. if not matched:
  108. raise HTTPException(status_code=404, detail="历史人物不存在")
  109. return {**figure.model_dump(), "_id": id}
  110. # 删除历史人物
  111. @router.delete("/figures/{id}")
  112. async def remove_figure(id: str):
  113. deleted = delete_figure(id)
  114. if not deleted:
  115. raise HTTPException(status_code=404, detail="历史人物不存在")
  116. return {"message": "删除成功", "id": id}
  117. # ===================== 润色接口 =====================
  118. # 润色接口
  119. @router.post("/rephrase")
  120. async def rephrase_as_figure(request: RephraseRequest):
  121. figure = get_figure_by_id(request.figureId)
  122. if not figure:
  123. raise HTTPException(status_code=404, detail="历史人物不存在")
  124. prompt = (
  125. f"你是{figure['name']},{figure['description']},生活在{figure['era']}。\n"
  126. f"请将以下话语改写成{figure['name']}的说话风格,保留原意,体现其性格特点({figure['prompt']})。\n"
  127. f"只输出改写后的内容,不要解释、不要加引号。\n"
  128. f"原文:{request.text}"
  129. )
  130. client = get_client()
  131. response = client.responses.create(
  132. model=Config.MODEL_NAME,
  133. input=[{"role": "user", "content": prompt}],
  134. stream=False,
  135. store=False,
  136. # thinking={"type":"auto"},
  137. )
  138. rephrased = ""
  139. for item in response.output:
  140. if hasattr(item, 'type') and item.type == 'message' and hasattr(item, 'content'):
  141. if isinstance(item.content, list):
  142. for content_item in item.content:
  143. if hasattr(content_item, 'text'):
  144. rephrased += content_item.text
  145. else:
  146. rephrased += str(item.content)
  147. if not rephrased:
  148. raise HTTPException(status_code=500, detail="AI未能生成润色结果")
  149. return {
  150. "original": request.text,
  151. "rephrased": rephrased,
  152. "figure": figure["name"],
  153. }