文章目錄
- 前言
- 一、fastapi的簡單入門
- 1:安裝必要的包(python=3.11):
- 2:快速搭建一個fastapi:
- 二、提升問答的響應速度
- 1. fastapi部署后端接口,在局域網內訪問的方法
- 2. 局域網內的測試:“未來科技有限公司的差旅費標準”PDF的RAG問答:
- 2.1 使用qwq32b模型(19GB)
- 2.2 使用qwen3:8b模型(5.2GB),平均30s
- 2.3 使用qwen3:8b模型(5.2GB),改進了rag_server的寫法,,平均18s
- 3. 如何進一步提速?
- 3.1 模型推理優化
- 3.2 調用鏈路優化
- 3.3 多模型協作(可以一試)
前言
在上一篇博客中,嘗試構建了多種mcp_server。接下來考慮,如何將我們的多mcp_server給前端提供api接口。
一、fastapi的簡單入門
參考文獻:https://www.bilibili.com/video/BV18F4m1K7N3
1:安裝必要的包(python=3.11):
conda activate langchain
pip install fastapi
2:快速搭建一個fastapi:
文件目錄如下圖:
# 導入必要的模塊
from fastapi import FastAPI # 導入 FastAPI 框架,用于構建 API
import uvicorn # 導入 Uvicorn,一個基于 asyncio 的 ASGI web 服務器,用于運行 FastAPI 應用
from fastapi import Request # 導入 Request 對象,用于獲取請求的詳細信息(如查詢參數、請求體等)
from fastapi.staticfiles import StaticFiles # 用于掛載靜態文件目錄(如圖片、CSS、JS 等)
from api.book import api_book # 導入圖書相關的路由模塊
from api.cbs import api_cbs # 導入出版社相關的路由模塊
from api.zz import api_zz # 導入作者相關的路由模塊# 創建 FastAPI 應用實例
app = FastAPI()# 掛載靜態文件目錄
# 將 "/upimg" 路徑映射到本地 "upimg" 文件夾,允許用戶通過 URL 訪問該目錄下的靜態文件(如上傳的圖片)
# 例如:訪問 http://127.0.0.1:8080/upimg/test.jpg 會返回 upimg/test.jpg 文件
# app.mount("/upimg", StaticFiles(directory="upimg"), name="upimg")# 注冊路由(API 子應用)
# 使用 include_router 將不同模塊的 API 路由注冊到主應用中,并設置統一前綴和標簽(用于文檔分類)
app.include_router(api_book, prefix="/book", tags=["圖書接口"]) # 圖書相關接口,路徑前綴為 /book
app.include_router(api_cbs, prefix="/cbs", tags=["出版社接口"]) # 出版社相關接口,路徑前綴為 /cbs
app.include_router(api_zz, prefix="/zz", tags=["作者接口"]) # 作者相關接口,路徑前綴為 /zz# 根路徑接口,用于測試服務是否正常運行
@app.get("/")
async def root():return {"message": "Hello World"}# 自定義測試接口,返回固定消息
@app.get("/xixi")
async def xixi():return {"message": "Hello xixi"}# GET 請求測試接口
# 接收查詢參數(query parameters),并打印到后端控制臺
@app.get("/get_test")
async def get_test(request: Request):get_test_message = request.query_params # 獲取 URL 中的查詢參數(如 ?name=abc&age=123)print("收到 GET 請求參數:", get_test_message) # 打印參數到控制臺return {"message": "Hello xixi"}# POST 請求測試接口
# 接收 JSON 格式的請求體(request body),并打印內容
@app.post("/post_test")
async def post_test(request: Request):post_test_message = await request.json() # 異步讀取請求體中的 JSON 數據print("收到 POST 請求數據:", post_test_message) # 打印接收到的 JSON 數據return {"message": "Hello xixi"}# 主程序入口
# 當直接運行此腳本時,啟動 Uvicorn 服務器
if __name__ == '__main__':# 運行 FastAPI 應用# 參數說明:# "myfastapi:app" -> 模塊名:應用實例名(即當前文件名為 myfastapi.py,app 是 FastAPI 實例)# host="127.0.0.1" -> 綁定本地回環地址# port=8080 -> 使用 8080 端口# reload=True -> 開啟熱重載,代碼修改后自動重啟服務(適合開發環境)uvicorn.run("myfastapi:app", host="127.0.0.1", port=8080, reload=True)
運行后可以瀏覽訪問http://127.0.0.1:8080/docs查看接口文檔
二、提升問答的響應速度
基于第一小節的fastapi,筆者搭建了一個chat接口,用于調用本地大模型。
1. fastapi部署后端接口,在局域網內訪問的方法
首先把ip改為0.0.0.0
,
if __name__ == '__main__':# uvicorn.run("myfastapi2:app", host="127.0.0.1", port=8080, reload=True)uvicorn.run("myfastapi2:app", host="0.0.0.0", port=8080, reload=True)
然后把本機的8080端口放開,具體操作,可參考博客,然后局域網內的其他主機就可以訪問了:
2. 局域網內的測試:“未來科技有限公司的差旅費標準”PDF的RAG問答:
PDF原文件部分截圖如下:
2.1 使用qwq32b模型(19GB)
在我的電腦上用postman測試下。結果還是比較準確的,對于這個文檔,RAG的效果還是可以的。可以看到一次提問的響應時間是52.39s。模型需要的響應時間太長了,接下來考慮如何提速。這里使用的是qwq32b模型(19GB),因此考慮換一個更小的模型試一下。
2.2 使用qwen3:8b模型(5.2GB),平均30s
這里使用qwen3:8b(5.2GB),使用相同的問題調用接口,結果如下。可以看到,看模型的輸出結果,也是沒有問題的,但是所需要的時間大幅度縮小,變為了26.55s。
2.3 使用qwen3:8b模型(5.2GB),改進了rag_server的寫法,,平均18s
通過chatgpt,進一步優化了rag_server,代碼如下:
async def get_answer(question: str) -> str:"""使用 RAG Chain 回答問題"""try:start = time.time()context_text = await retriever.ainvoke(question.strip())# LLM 調用start_llm = time.time()final_prompt = prompt.format(context=context_text, input=question)response = await llm.ainvoke(final_prompt)logger.info(f"🟢 LLM 生成完成,耗時: {time.time() - start_llm:.2f}s")logger.info(f"? 總耗時: {time.time() - start:.2f}s")return StrOutputParser().invoke(response)except Exception as e:logger.exception("? 問答過程中發生異常")return f"問答過程中出錯:{str(e)}"
3. 如何進一步提速?
3.1 模型推理優化
(1) 使用更小的模型;(暫不采納)
(2) 減少生成的token數;
LLM推理時間 = 上下文長度 + 輸出長度;如果只是回答文檔中的問題,不必生成很長的上下文,在 LangChain 調用時傳:llm = ChatOllama(model="qwen3:8b", temperature=0, num_predict=200)
num_predict默認是256?
讓我們來試一下看看效果。設num_predict=200,時間數據表明,的確提速了!但是,答案被強制截斷了,輸出是不完整的,只輸出了< think >中的內容。
(3) 控制上下文窗口(prompt壓縮)(暫不采納)
Qwen3:8b 的上下文窗口很大,但你傳進去的 context 越長,推理速度越慢
檢索 k=1 是很好的,但也要確保 doc 片段不要太長
可以在構造 RAG 時裁剪:doc.page_content[:1000] # 控制單片段長度
(4) 調整GPU批處理參數
Ollama 的后端支持 kv_cache 批量生成,在啟動模型時可設置:ollama run qwen3:8b --num-thread 16 --num-gpu 1
已經100%運行在GPU上了。
3.2 調用鏈路優化
(1) 流式輸出(可行,這樣從向量數據庫中查詢的過程所花的時間不可省略,但是最后給LLM所得到的LLM的輸出可以流式輸出,給人一種時間縮短的感覺)
讓前端/調用方邊生成邊顯示,不用等 8~9 秒全部生成完才顯示
LangChain 里可以改成:
async for chunk in llm.astream(final_prompt):print(chunk.content, end="", flush=True)
(2) 緩存相似查詢(可以一試)
對相同 embedding 檢索結果的 Prompt → LLM 輸出做緩存;
Python 可以直接用 functools.lru_cache 或 Redis 做緩存
(3) 避免重復加載模型(可行)
Ollama 第一次加載模型會花幾秒,之后在 GPU 內存中保持熱啟動;
確保你的進程是長駐的(FastAPI、MCP Server 不要頻繁重啟)
3.3 多模型協作(可以一試)
如果不追求每一步都是 Qwen3:8b 的高質量輸出,可以用:
小模型(3B/4B) 先做快速判斷 + 檢索 → 再由大模型做最終生成;
這樣 Qwen3:8b 的推理時間可以減少 %-%