File size: 6,135 Bytes
9a71f08 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | #!/usr/bin/env python3
"""
VetNet DAPT 推理服务 — DAPT 微调 Qwen2.5-3B + FAISS RAG + LCPS 2.0
启动:
VET_MODEL=./vet-qwen3b-v3-dapt KB_DIR=./knowledge_base_simple python server_gpu.py
API 端点:
GET /health — 健康检查 + 模型状态
POST /run — LCPS 推理 + SOAP (兼容旧接口)
POST /api/v1/diagnose — LCPS 诊断 (VetCopilot 标准接口)
POST /chat — 兽医自由问答
"""
import os, sys, time, json, logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
log = logging.getLogger('vetnet-gpu')
MODEL = None
RAG = None
MODEL_INFO = {}
class RunRequest(BaseModel):
text: str
species: str = "狗"
class ChatRequest(BaseModel):
question: str
species: str = "狗"
class DiagnoseRequest(BaseModel):
"""VetCopilot 标准诊断请求"""
dialogue: str
species: str = "狗"
@asynccontextmanager
async def lifespan(app: FastAPI):
global MODEL, RAG, MODEL_INFO
model_path = os.getenv("VET_MODEL", "./vet-qwen3b-v3-dapt")
kb_dir = os.getenv("KB_DIR", "./knowledge_base_simple")
log.info("=" * 60)
log.info("VetNet DAPT 推理服务 v3.1 启动")
log.info(f"模型: {model_path}")
log.info(f"知识库: {kb_dir}")
log.info(f"引擎: DAPT Qwen2.5-3B + LCPS 2.0 + FAISS RAG")
log.info("=" * 60)
MODEL_INFO = {
"name": "vet-qwen3b-v3-dapt",
"base_model": "Qwen2.5-3B-Instruct",
"training": "DAPT (Domain-Adaptive Pretraining)",
"engine": "LCPS 2.0",
}
t0 = time.time()
# 加载 RAG
try:
from model.rag_faiss import FAISSRetriever
RAG = FAISSRetriever(kb_dir=kb_dir, device="cuda")
RAG.load()
log.info(f"RAG 加载完成 ({RAG.get_stats()})")
except Exception as e:
log.warning(f"RAG 加载失败 (知识库文件缺失): {e}")
RAG = None
# 加载 DAPT 模型
from model.qwen_local import QwenLocalClient
MODEL = QwenLocalClient(
model_path=model_path,
temperature=float(os.getenv("VET_TEMPERATURE", "0.1")), # 降低温度减少误诊随机性
max_tokens=int(os.getenv("VET_MAX_TOKENS", "8192")),
load_in_4bit=False,
device_map="auto",
seed=int(os.getenv("VET_SEED", "42")), # 固定 seed 保证可复现
)
MODEL._ensure_loaded()
MODEL_INFO["load_time_ms"] = int((time.time() - t0) * 1000)
log.info(f"模型加载完成 ({MODEL_INFO['load_time_ms']}ms)")
yield
if MODEL:
MODEL.unload()
log.info("服务关闭")
app = FastAPI(title="VetNet GPU v3.0", lifespan=lifespan)
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
@app.get("/health")
def health():
return {
"status": "ok",
"model_loaded": MODEL is not None and MODEL.ping() if MODEL else False,
"model_info": MODEL_INFO,
"rag": RAG.get_stats() if RAG else {"ready": False, "vectors": 0, "chunks": 0},
}
@app.post("/run")
def run(req: RunRequest):
"""LCPS 推理 + SOAP 生成 (兼容旧接口)"""
return _do_diagnose(req.text, req.species)
@app.post("/api/v1/diagnose")
def diagnose(req: DiagnoseRequest):
"""VetCopilot 标准 LCPS 诊断接口"""
return _do_diagnose(req.dialogue, req.species)
def _do_diagnose(text: str, species: str) -> dict:
"""统一诊断逻辑"""
if not MODEL:
raise HTTPException(503, "模型未加载")
t0 = time.time()
try:
from model.lcps import lcps_single_shot, generate_soap_from_lcps
rag = RAG if RAG and RAG._ready else None
result = lcps_single_shot(text, species, MODEL, rag)
soap = generate_soap_from_lcps(result["lcps_chain"], text, MODEL)
elapsed = int((time.time() - t0) * 1000)
return {
"success": True,
"diagnosis": result.get("primary_diagnosis", ""),
"primary_diagnosis": result.get("primary_diagnosis", ""),
"differential_list": result.get("differential_list", []),
"must_not_miss": result.get("must_not_miss", []),
"soap": soap.get("soap", {}),
"lcps_chain": result.get("lcps_chain", {}),
"summary": result.get("prioritisation_rationale", ""),
"kb_used": rag is not None,
"engine": "dapt-lcps-v3.1",
"model": MODEL_INFO.get("name", "vet-qwen3b-v3-dapt"),
"total_ms": elapsed,
"elapsed_ms": elapsed,
"error": "",
}
except Exception as e:
log.exception("推理失败")
return {
"success": False,
"diagnosis": "",
"primary_diagnosis": "",
"differential_list": [],
"must_not_miss": [],
"soap": {},
"lcps_chain": {},
"summary": "",
"engine": "dapt-lcps-v3.1",
"model": MODEL_INFO.get("name", ""),
"elapsed_ms": int((time.time() - t0) * 1000),
"error": str(e),
}
@app.post("/chat")
def chat(req: ChatRequest):
if not MODEL:
raise HTTPException(503, "模型未加载")
try:
kb_context = RAG.retrieve(f"{req.species} {req.question[:300]}") if RAG else ""
system = "你是一位经验丰富的兽医专家。请用专业但易懂的语言回答以下问题。"
if kb_context:
system += f"\n\n参考知识:\n{kb_context}"
answer = MODEL.chat(system, f"物种: {req.species}\n问题: {req.question}")
return {"answer": answer, "kb_used": bool(kb_context)}
except Exception as e:
log.exception("问答失败")
raise HTTPException(500, str(e))
if __name__ == "__main__":
import uvicorn
port = int(os.getenv("PORT", "8000"))
uvicorn.run(app, host="0.0.0.0", port=port)
|