god.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. from fastapi import APIRouter, Depends, HTTPException, status
  2. from fastapi.responses import StreamingResponse
  3. from typing import List, Annotated, Any
  4. import json
  5. import logging
  6. from app.db.session import get_db
  7. from app.schemas import PersonaResponse, GodGenerateRequest, PersonaCreate
  8. from app.crud import create_persona
  9. from app.api.deps import get_current_user
  10. # from app.agent.god import God # Deprecated
  11. from app.agent.real_god import RealGodAgent
  12. from app.core.async_utils import async_generator_wrapper
  13. from app.core.cache import cache_service
  14. from app.services.persona_service import persona_service
  15. from app.core.config import settings
  16. logger = logging.getLogger(__name__)
  17. router = APIRouter()
  18. # god = God() # Deprecated
  19. # @router.post("/generate", response_model=List[PersonaResponse])
  20. # def generate_personas(
  21. # request: GodGenerateRequest,
  22. # current_user: Annotated[Any, Depends(get_current_user)],
  23. # db: Any = Depends(get_db)
  24. # ):
  25. # """
  26. # Generate personas based on natural language prompt using the God agent.
  27. # DEPRECATED: Use /generate_real instead.
  28. # """
  29. # raise HTTPException(status_code=410, detail="This endpoint is deprecated. Use RealGodAgent.")
  30. @router.post("/generate_real")
  31. async def generate_real_personas(
  32. request: GodGenerateRequest,
  33. current_user: Annotated[Any, Depends(get_current_user)],
  34. db: Any = Depends(get_db)
  35. ):
  36. """
  37. Generate personas using RealGodAgent with internet search capabilities.
  38. Each persona is generated sequentially to ensure high quality and deep research.
  39. Returns a StreamingResponse with SSE events.
  40. """
  41. if not settings.has_api_key:
  42. raise HTTPException(
  43. status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
  44. detail="模型服务尚未配置,暂时无法生成角色。请联系管理员配置 API Key。",
  45. )
  46. agent = RealGodAgent()
  47. user_id = current_user.id
  48. # 1. Fetch all existing persona names from DB for global deduplication
  49. try:
  50. rs = db.execute("SELECT name FROM personas")
  51. # rs.fetchall() returns list of Row objects or tuples?
  52. # fetch_all returns list of RowObject
  53. from app.db.client import fetch_all
  54. rows = fetch_all(rs)
  55. db_existing_names = [r.name for r in rows if hasattr(r, 'name')]
  56. except Exception as e:
  57. logger.error(f"Error fetching existing names: {e}")
  58. db_existing_names = []
  59. async def event_generator():
  60. try:
  61. generated_names_in_session = []
  62. saved_persona_count = 0
  63. # Use n=None to allow the agent to auto-detect count from prompt
  64. target_n = request.n if request.n > 1 else None
  65. async for event in async_generator_wrapper(agent.run(request.prompt, n=target_n, generated_names=generated_names_in_session, db_existing_names=db_existing_names)):
  66. if event.get("type") == "error":
  67. yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
  68. return
  69. # If result, save to DB
  70. if event["type"] == "result":
  71. personas_data = event["content"]
  72. saved_personas_dicts = []
  73. # Ensure it's a list
  74. if isinstance(personas_data, dict):
  75. personas_data = [personas_data]
  76. for p_data in personas_data:
  77. # Add name to session list
  78. # Safe check for name
  79. if isinstance(p_data, dict) and p_data.get('name'):
  80. generated_names_in_session.append(p_data['name'])
  81. # Use unified service
  82. try:
  83. if not isinstance(p_data, dict):
  84. logger.error(f"Invalid persona data format: {p_data}")
  85. continue
  86. # Log debug info
  87. msg_content = f"正在保存角色: {p_data.get('name', 'Unknown')}..."
  88. yield f"data: {json.dumps({'type': 'status', 'content': msg_content}, ensure_ascii=False)}\n\n"
  89. saved_p = persona_service.save_generated_persona(user_id, p_data, db=db)
  90. if saved_p:
  91. # Parse theories from JSON string to List if needed
  92. theories_val = saved_p.theories
  93. if isinstance(theories_val, str):
  94. try:
  95. theories_val = json.loads(theories_val)
  96. except:
  97. theories_val = []
  98. # Convert to dict for JSON serialization
  99. saved_dict = {
  100. "id": saved_p.id,
  101. "name": saved_p.name,
  102. "title": saved_p.title,
  103. "bio": saved_p.bio,
  104. "theories": theories_val,
  105. "stance": saved_p.stance,
  106. "system_prompt": saved_p.system_prompt,
  107. "is_public": saved_p.is_public
  108. }
  109. saved_personas_dicts.append(saved_dict)
  110. saved_persona_count += 1
  111. success_msg = f"✅ 角色 {saved_p.name} 保存成功 (ID: {saved_p.id})"
  112. yield f"data: {json.dumps({'type': 'status', 'content': success_msg}, ensure_ascii=False)}\n\n"
  113. # CRITICAL: Ensure cache is invalidated for the list view
  114. cache_service.delete_keys_pattern(f"personas:list:{user_id}:*")
  115. else:
  116. fail_msg = f"角色 {p_data.get('name')} 保存失败,请查看后台日志"
  117. yield f"data: {json.dumps({'type': 'error', 'content': fail_msg}, ensure_ascii=False)}\n\n"
  118. return
  119. except Exception as e:
  120. logger.error(f"Error saving real persona: {e}")
  121. err_msg = "角色保存失败,请稍后重试"
  122. yield f"data: {json.dumps({'type': 'error', 'content': err_msg}, ensure_ascii=False)}\n\n"
  123. return
  124. # Update content with saved personas (including IDs)
  125. event["content"] = saved_personas_dicts
  126. yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
  127. if saved_persona_count:
  128. final_msg = "✅ 所有智能体角色已生成并保存完毕。已停止生成。"
  129. yield f"data: {json.dumps({'type': 'thought', 'content': final_msg}, ensure_ascii=False)}\n\n"
  130. else:
  131. yield f"data: {json.dumps({'type': 'error', 'content': '未能生成可保存的角色,请稍后重试'}, ensure_ascii=False)}\n\n"
  132. except Exception as e:
  133. logger.error(f"RealGod stream error: {e}")
  134. err_msg = "角色生成失败,请稍后重试"
  135. yield f"data: {json.dumps({'type': 'error', 'content': err_msg}, ensure_ascii=False)}\n\n"
  136. return StreamingResponse(event_generator(), media_type="text/event-stream")