|
|
@@ -0,0 +1,154 @@
|
|
|
+from fastapi import APIRouter, HTTPException
|
|
|
+from pydantic import BaseModel
|
|
|
+from typing import Optional
|
|
|
+from app.graphs.chat_graph import create_chat_graph
|
|
|
+from app.graphs.analysis_graph import create_analysis_graph
|
|
|
+import uuid
|
|
|
+import time
|
|
|
+
|
|
|
+router = APIRouter(prefix="/api/v1", tags=["adapter"])
|
|
|
+
|
|
|
+# ---- Dify-compatible request/response models ----
|
|
|
+
|
|
|
+class DifyMessage(BaseModel):
|
|
|
+ role: str
|
|
|
+ content: str
|
|
|
+
|
|
|
+class DifyChatRequest(BaseModel):
|
|
|
+ query: str = ""
|
|
|
+ user: str = "anonymous"
|
|
|
+ conversation_id: str = ""
|
|
|
+ messages: list[DifyMessage] = []
|
|
|
+ inputs: dict = {}
|
|
|
+ response_mode: str = "blocking" # blocking / streaming
|
|
|
+ user_id: str = ""
|
|
|
+ bot_name: str = "AI管家"
|
|
|
+
|
|
|
+class DifyAnalysisRequest(BaseModel):
|
|
|
+ report_id: Optional[int] = None
|
|
|
+ user_id: Optional[str] = None
|
|
|
+ focus: Optional[str] = None
|
|
|
+ messages: list[DifyMessage] = []
|
|
|
+
|
|
|
+class DifyChoice(BaseModel):
|
|
|
+ index: int
|
|
|
+ message: dict
|
|
|
+ finish_reason: str = "stop"
|
|
|
+
|
|
|
+class DifyUsage(BaseModel):
|
|
|
+ prompt_tokens: int = 0
|
|
|
+ completion_tokens: int = 0
|
|
|
+ total_tokens: int = 0
|
|
|
+
|
|
|
+class DifyResponse(BaseModel):
|
|
|
+ id: str
|
|
|
+ object: str = "chat.completion"
|
|
|
+ created: int
|
|
|
+ model: str = "langgraph-cfc"
|
|
|
+ choices: list[DifyChoice]
|
|
|
+ usage: DifyUsage = DifyUsage()
|
|
|
+ metadata: dict = {}
|
|
|
+
|
|
|
+# ---- Helpers ----
|
|
|
+
|
|
|
+def _now_ts() -> int:
|
|
|
+ return int(time.time())
|
|
|
+
|
|
|
+def _extract_query(req: DifyChatRequest) -> str:
|
|
|
+ if req.query:
|
|
|
+ return req.query
|
|
|
+ # fallback: take last user message
|
|
|
+ for msg in reversed(req.messages):
|
|
|
+ if msg.role == "user":
|
|
|
+ return msg.content
|
|
|
+ return ""
|
|
|
+
|
|
|
+def _to_langgraph_context(req: DifyChatRequest) -> dict:
|
|
|
+ ctx = {}
|
|
|
+ if isinstance(req.inputs, dict):
|
|
|
+ ctx["child_id"] = req.inputs.get("child_id")
|
|
|
+ ctx["report_id"] = req.inputs.get("report_id")
|
|
|
+ ctx["family_id"] = req.inputs.get("family_id")
|
|
|
+ return ctx
|
|
|
+
|
|
|
+# ---- Dify-compatible endpoints ----
|
|
|
+
|
|
|
+@router.post("/chat/completion", response_model=DifyResponse)
|
|
|
+async def chat_completion(req: DifyChatRequest):
|
|
|
+ query = _extract_query(req)
|
|
|
+ if not query:
|
|
|
+ raise HTTPException(status_code=400, detail="query 为空")
|
|
|
+
|
|
|
+ graph = create_chat_graph()
|
|
|
+ initial_state = {
|
|
|
+ "query": query,
|
|
|
+ "user_id": int(req.user_id) if str(req.user_id).isdigit() else 0,
|
|
|
+ "conversation_id": req.conversation_id or None,
|
|
|
+ "intent": None,
|
|
|
+ "context": _to_langgraph_context(req),
|
|
|
+ "messages": None,
|
|
|
+ "answer": None,
|
|
|
+ "tasks": [],
|
|
|
+ "sources": [],
|
|
|
+ }
|
|
|
+ config = {
|
|
|
+ "configurable": {"thread_id": req.conversation_id or str(req.user_id or "default")},
|
|
|
+ }
|
|
|
+ result = await graph.ainvoke(initial_state, config)
|
|
|
+ answer = result.get("answer") or ""
|
|
|
+ return DifyResponse(
|
|
|
+ id=f"chatcmpl-{uuid.uuid4().hex[:24]}",
|
|
|
+ created=_now_ts(),
|
|
|
+ choices=[
|
|
|
+ DifyChoice(
|
|
|
+ index=0,
|
|
|
+ message={"role": "assistant", "content": answer},
|
|
|
+ finish_reason="stop",
|
|
|
+ )
|
|
|
+ ],
|
|
|
+ usage=DifyUsage(
|
|
|
+ prompt_tokens=len(query.split()),
|
|
|
+ completion_tokens=len(answer.split()),
|
|
|
+ total_tokens=len(query.split()) + len(answer.split()),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@router.post("/analysis/run", response_model=DifyResponse)
|
|
|
+async def analysis_run(req: DifyAnalysisRequest):
|
|
|
+ graph = create_analysis_graph()
|
|
|
+ report_id = req.report_id
|
|
|
+ if report_id is None and isinstance(req.inputs, dict):
|
|
|
+ report_id = req.inputs.get("report_id")
|
|
|
+ initial_state = {
|
|
|
+ "report_id": report_id,
|
|
|
+ "user_id": int(req.user_id) if str(req.user_id).isdigit() else 0,
|
|
|
+ "focus": req.focus,
|
|
|
+ "report_data": None,
|
|
|
+ "survey_data": None,
|
|
|
+ "dimension_scores": None,
|
|
|
+ "analysis": None,
|
|
|
+ "recommendations": [],
|
|
|
+ }
|
|
|
+ result = await graph.ainvoke(initial_state, {})
|
|
|
+ analysis = result.get("analysis") or ""
|
|
|
+ recommendations = result.get("recommendations") or []
|
|
|
+ content = analysis
|
|
|
+ if recommendations:
|
|
|
+ content += "\n\n建议:\n" + "\n".join(f"- {r}" for r in recommendations)
|
|
|
+ return DifyResponse(
|
|
|
+ id=f"analysis-{uuid.uuid4().hex[:24]}",
|
|
|
+ created=_now_ts(),
|
|
|
+ choices=[
|
|
|
+ DifyChoice(
|
|
|
+ index=0,
|
|
|
+ message={"role": "assistant", "content": content},
|
|
|
+ finish_reason="stop",
|
|
|
+ )
|
|
|
+ ],
|
|
|
+ usage=DifyUsage(
|
|
|
+ prompt_tokens=len((req.focus or "").split()),
|
|
|
+ completion_tokens=len(content.split()),
|
|
|
+ total_tokens=len((req.focus or "").split()) + len(content.split()),
|
|
|
+ ),
|
|
|
+ )
|