Files
xianjing-ai/engine/app/main.py
T
Cursor Agentandhaijd b7bd830da7 feat: 用 Ragflow 搭建鲜境方案客服
咨询台接 Ragflow 检索与示例知识库,支持追问、判定、引用和留资。

Co-authored-by: haijd <haijd@163.com>
2026-09-20 01:08:11 +00:00

153 lines
4.4 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Field
from .config_loader import business_config
from .leads import add_lead, list_leads
from .ragflow_client import RagflowClient, RagflowError
from .scheme_engine import create_session, handle_message
from .session_store import store
from .settings import ROOT, settings
STATIC_DIR = Path(__file__).resolve().parent / "static"
CORPUS_DIR = ROOT / "knowledge" / "corpus"
app = FastAPI(title=settings.app_name, version="0.2.0")
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
class ChatRequest(BaseModel):
message: str = Field(..., min_length=1)
session_id: str | None = None
class SessionResponse(BaseModel):
session_id: str
class HandoffRequest(BaseModel):
session_id: str
name: str = Field(..., min_length=1, max_length=40)
phone: str = Field(..., min_length=5, max_length=30)
note: str = ""
@app.get("/")
def index() -> FileResponse:
return FileResponse(STATIC_DIR / "index.html")
@app.get("/api/health")
def health() -> dict:
rag = RagflowClient().health()
return {
"ok": True,
"app": settings.app_name,
"retrieval_backend": settings.retrieval_backend,
"slots": len(business_config.slots),
"rules": len(business_config.rules),
"ragflow": rag,
"ragflow_web": settings.ragflow_web_url,
"dataset_ids_configured": bool(settings.ragflow_dataset_ids.strip()),
}
@app.get("/api/config/slots")
def get_slots() -> dict:
return business_config.slots_doc
@app.get("/api/config/decision-tree")
def get_tree() -> dict:
return business_config.tree_doc
@app.post("/api/admin/reload-config")
def reload_config() -> dict:
business_config.reload()
return {"reloaded": True, "slots": len(business_config.slots), "rules": len(business_config.rules)}
@app.get("/api/knowledge/files")
def knowledge_files() -> dict:
files = []
if CORPUS_DIR.exists():
for path in sorted(CORPUS_DIR.glob("*.md")):
files.append({"name": path.name, "bytes": path.stat().st_size})
return {"files": files}
@app.get("/api/ragflow/status")
def ragflow_status() -> dict:
client = RagflowClient()
health = client.health()
datasets: list[dict] = []
error = ""
if health.get("ok") and settings.ragflow_api_key:
try:
for item in client.list_datasets():
datasets.append(
{
"id": item.get("id"),
"name": item.get("name"),
"document_count": item.get("document_count"),
"chunk_count": item.get("chunk_count"),
}
)
except Exception as exc:
error = str(exc)
return {
"health": health,
"web": settings.ragflow_web_url,
"api_key_set": bool(settings.ragflow_api_key),
"dataset_ids": [x for x in settings.ragflow_dataset_ids.split(",") if x.strip()],
"datasets": datasets,
"error": error,
}
@app.get("/api/leads")
def api_leads() -> dict:
return {"leads": list_leads()}
@app.post("/api/sessions", response_model=SessionResponse)
def api_create_session() -> SessionResponse:
session = create_session()
return SessionResponse(session_id=session.id)
@app.post("/api/chat")
def api_chat(body: ChatRequest) -> dict:
session_id = body.session_id
if not session_id:
session_id = create_session().id
try:
return handle_message(session_id, body.message)
except KeyError:
raise HTTPException(status_code=404, detail="session_not_found") from None
@app.post("/api/handoff")
def api_handoff(body: HandoffRequest) -> dict:
session = store.get(body.session_id)
slots = session.slots if session else {}
scheme = session.last_scheme if session else None
lead_id = add_lead(
session_id=body.session_id,
name=body.name.strip(),
phone=body.phone.strip(),
note=body.note.strip(),
slots_json=json.dumps(slots, ensure_ascii=False),
scheme_json=json.dumps(scheme or {}, ensure_ascii=False),
)
if session:
session.phase = "handoff"
return {"ok": True, "lead_id": lead_id}