| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- from fastapi import APIRouter, Depends, HTTPException, status
- from fastapi.responses import StreamingResponse
- from typing import List, Annotated, Any
- import json
- import logging
- from app.db.session import get_db
- from app.schemas import PersonaResponse, GodGenerateRequest, PersonaCreate
- from app.crud import create_persona
- from app.api.deps import get_current_user
- # from app.agent.god import God # Deprecated
- from app.agent.real_god import RealGodAgent
- from app.core.async_utils import async_generator_wrapper
- from app.core.cache import cache_service
- from app.services.persona_service import persona_service
- from app.core.config import settings
- logger = logging.getLogger(__name__)
- router = APIRouter()
- # god = God() # Deprecated
- # @router.post("/generate", response_model=List[PersonaResponse])
- # def generate_personas(
- # request: GodGenerateRequest,
- # current_user: Annotated[Any, Depends(get_current_user)],
- # db: Any = Depends(get_db)
- # ):
- # """
- # Generate personas based on natural language prompt using the God agent.
- # DEPRECATED: Use /generate_real instead.
- # """
- # raise HTTPException(status_code=410, detail="This endpoint is deprecated. Use RealGodAgent.")
- @router.post("/generate_real")
- async def generate_real_personas(
- request: GodGenerateRequest,
- current_user: Annotated[Any, Depends(get_current_user)],
- db: Any = Depends(get_db)
- ):
- """
- Generate personas using RealGodAgent with internet search capabilities.
- Each persona is generated sequentially to ensure high quality and deep research.
- Returns a StreamingResponse with SSE events.
- """
- if not settings.has_api_key:
- raise HTTPException(
- status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
- detail="模型服务尚未配置,暂时无法生成角色。请联系管理员配置 API Key。",
- )
- agent = RealGodAgent()
- user_id = current_user.id
-
- # 1. Fetch all existing persona names from DB for global deduplication
- try:
- rs = db.execute("SELECT name FROM personas")
- # rs.fetchall() returns list of Row objects or tuples?
- # fetch_all returns list of RowObject
- from app.db.client import fetch_all
- rows = fetch_all(rs)
- db_existing_names = [r.name for r in rows if hasattr(r, 'name')]
- except Exception as e:
- logger.error(f"Error fetching existing names: {e}")
- db_existing_names = []
- async def event_generator():
- try:
- generated_names_in_session = []
- saved_persona_count = 0
-
- # Use n=None to allow the agent to auto-detect count from prompt
- target_n = request.n if request.n > 1 else None
-
- 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)):
- if event.get("type") == "error":
- yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
- return
-
- # If result, save to DB
- if event["type"] == "result":
- personas_data = event["content"]
- saved_personas_dicts = []
-
- # Ensure it's a list
- if isinstance(personas_data, dict):
- personas_data = [personas_data]
-
- for p_data in personas_data:
- # Add name to session list
- # Safe check for name
- if isinstance(p_data, dict) and p_data.get('name'):
- generated_names_in_session.append(p_data['name'])
-
- # Use unified service
- try:
- if not isinstance(p_data, dict):
- logger.error(f"Invalid persona data format: {p_data}")
- continue
-
- # Log debug info
- msg_content = f"正在保存角色: {p_data.get('name', 'Unknown')}..."
- yield f"data: {json.dumps({'type': 'status', 'content': msg_content}, ensure_ascii=False)}\n\n"
-
- saved_p = persona_service.save_generated_persona(user_id, p_data, db=db)
-
- if saved_p:
- # Parse theories from JSON string to List if needed
- theories_val = saved_p.theories
- if isinstance(theories_val, str):
- try:
- theories_val = json.loads(theories_val)
- except:
- theories_val = []
- # Convert to dict for JSON serialization
- saved_dict = {
- "id": saved_p.id,
- "name": saved_p.name,
- "title": saved_p.title,
- "bio": saved_p.bio,
- "theories": theories_val,
- "stance": saved_p.stance,
- "system_prompt": saved_p.system_prompt,
- "is_public": saved_p.is_public
- }
- saved_personas_dicts.append(saved_dict)
- saved_persona_count += 1
- success_msg = f"✅ 角色 {saved_p.name} 保存成功 (ID: {saved_p.id})"
- yield f"data: {json.dumps({'type': 'status', 'content': success_msg}, ensure_ascii=False)}\n\n"
-
- # CRITICAL: Ensure cache is invalidated for the list view
- cache_service.delete_keys_pattern(f"personas:list:{user_id}:*")
- else:
- fail_msg = f"角色 {p_data.get('name')} 保存失败,请查看后台日志"
- yield f"data: {json.dumps({'type': 'error', 'content': fail_msg}, ensure_ascii=False)}\n\n"
- return
-
- except Exception as e:
- logger.error(f"Error saving real persona: {e}")
- err_msg = "角色保存失败,请稍后重试"
- yield f"data: {json.dumps({'type': 'error', 'content': err_msg}, ensure_ascii=False)}\n\n"
- return
-
- # Update content with saved personas (including IDs)
- event["content"] = saved_personas_dicts
-
- yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
-
- if saved_persona_count:
- final_msg = "✅ 所有智能体角色已生成并保存完毕。已停止生成。"
- yield f"data: {json.dumps({'type': 'thought', 'content': final_msg}, ensure_ascii=False)}\n\n"
- else:
- yield f"data: {json.dumps({'type': 'error', 'content': '未能生成可保存的角色,请稍后重试'}, ensure_ascii=False)}\n\n"
-
- except Exception as e:
- logger.error(f"RealGod stream error: {e}")
- err_msg = "角色生成失败,请稍后重试"
- yield f"data: {json.dumps({'type': 'error', 'content': err_msg}, ensure_ascii=False)}\n\n"
- return StreamingResponse(event_generator(), media_type="text/event-stream")
|