adapter.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. from fastapi import APIRouter, HTTPException
  2. from pydantic import BaseModel
  3. from typing import Optional
  4. from app.graphs.chat_graph import create_chat_graph
  5. from app.graphs.analysis_graph import create_analysis_graph
  6. import uuid
  7. import time
  8. router = APIRouter(prefix="/api/v1", tags=["adapter"])
  9. # ---- Dify-compatible request/response models ----
  10. class DifyMessage(BaseModel):
  11. role: str
  12. content: str
  13. class DifyChatRequest(BaseModel):
  14. query: str = ""
  15. user: str = "anonymous"
  16. conversation_id: str = ""
  17. messages: list[DifyMessage] = []
  18. inputs: dict = {}
  19. response_mode: str = "blocking" # blocking / streaming
  20. user_id: str = ""
  21. bot_name: str = "AI管家"
  22. class DifyAnalysisRequest(BaseModel):
  23. report_id: Optional[int] = None
  24. user_id: Optional[str] = None
  25. focus: Optional[str] = None
  26. messages: list[DifyMessage] = []
  27. class DifyChoice(BaseModel):
  28. index: int
  29. message: dict
  30. finish_reason: str = "stop"
  31. class DifyUsage(BaseModel):
  32. prompt_tokens: int = 0
  33. completion_tokens: int = 0
  34. total_tokens: int = 0
  35. class DifyResponse(BaseModel):
  36. id: str
  37. object: str = "chat.completion"
  38. created: int
  39. model: str = "langgraph-cfc"
  40. choices: list[DifyChoice]
  41. usage: DifyUsage = DifyUsage()
  42. metadata: dict = {}
  43. # ---- Helpers ----
  44. def _now_ts() -> int:
  45. return int(time.time())
  46. def _extract_query(req: DifyChatRequest) -> str:
  47. if req.query:
  48. return req.query
  49. # fallback: take last user message
  50. for msg in reversed(req.messages):
  51. if msg.role == "user":
  52. return msg.content
  53. return ""
  54. def _to_langgraph_context(req: DifyChatRequest) -> dict:
  55. ctx = {}
  56. if isinstance(req.inputs, dict):
  57. ctx["child_id"] = req.inputs.get("child_id")
  58. ctx["report_id"] = req.inputs.get("report_id")
  59. ctx["family_id"] = req.inputs.get("family_id")
  60. return ctx
  61. # ---- Dify-compatible endpoints ----
  62. @router.post("/chat/completion", response_model=DifyResponse)
  63. async def chat_completion(req: DifyChatRequest):
  64. query = _extract_query(req)
  65. if not query:
  66. raise HTTPException(status_code=400, detail="query 为空")
  67. graph = create_chat_graph()
  68. initial_state = {
  69. "query": query,
  70. "user_id": int(req.user_id) if str(req.user_id).isdigit() else 0,
  71. "conversation_id": req.conversation_id or None,
  72. "intent": None,
  73. "context": _to_langgraph_context(req),
  74. "messages": None,
  75. "answer": None,
  76. "tasks": [],
  77. "sources": [],
  78. }
  79. config = {
  80. "configurable": {"thread_id": req.conversation_id or str(req.user_id or "default")},
  81. }
  82. result = await graph.ainvoke(initial_state, config)
  83. answer = result.get("answer") or ""
  84. return DifyResponse(
  85. id=f"chatcmpl-{uuid.uuid4().hex[:24]}",
  86. created=_now_ts(),
  87. choices=[
  88. DifyChoice(
  89. index=0,
  90. message={"role": "assistant", "content": answer},
  91. finish_reason="stop",
  92. )
  93. ],
  94. usage=DifyUsage(
  95. prompt_tokens=len(query.split()),
  96. completion_tokens=len(answer.split()),
  97. total_tokens=len(query.split()) + len(answer.split()),
  98. ),
  99. )
  100. @router.post("/analysis/run", response_model=DifyResponse)
  101. async def analysis_run(req: DifyAnalysisRequest):
  102. graph = create_analysis_graph()
  103. report_id = req.report_id
  104. if report_id is None and isinstance(req.inputs, dict):
  105. report_id = req.inputs.get("report_id")
  106. initial_state = {
  107. "report_id": report_id,
  108. "user_id": int(req.user_id) if str(req.user_id).isdigit() else 0,
  109. "focus": req.focus,
  110. "report_data": None,
  111. "survey_data": None,
  112. "dimension_scores": None,
  113. "analysis": None,
  114. "recommendations": [],
  115. }
  116. result = await graph.ainvoke(initial_state, {})
  117. analysis = result.get("analysis") or ""
  118. recommendations = result.get("recommendations") or []
  119. content = analysis
  120. if recommendations:
  121. content += "\n\n建议:\n" + "\n".join(f"- {r}" for r in recommendations)
  122. return DifyResponse(
  123. id=f"analysis-{uuid.uuid4().hex[:24]}",
  124. created=_now_ts(),
  125. choices=[
  126. DifyChoice(
  127. index=0,
  128. message={"role": "assistant", "content": content},
  129. finish_reason="stop",
  130. )
  131. ],
  132. usage=DifyUsage(
  133. prompt_tokens=len((req.focus or "").split()),
  134. completion_tokens=len(content.split()),
  135. total_tokens=len((req.focus or "").split()) + len(content.split()),
  136. ),
  137. )