大模型本地部署與API服務教程

大模型本地部署與API服務教程

目標:在Ubuntu服務器部署本地大模型,并提供API服務,支持局域網下的Windows客戶端調用。
支持兩種部署方式:① 自建FastAPI服務(高定制) ② 使用Ollama(極簡快速)
源碼倉庫:BUAA_A503/glm-ollama-api


文章目錄

  • 大模型本地部署與API服務教程
    • 1. 硬件與系統準備
      • 1.1 服務端要求
      • 1.2 客戶端要求
    • 2. 安裝Python環境與包管理工具 `uv`
      • 2.1 安裝 Python 3.10+
      • 2.2 安裝 `uv`
    • 3. 創建虛擬環境并安裝依賴
      • 3.1 創建虛擬環境
      • 3.2 服務器端安裝核心依賴
      • 3.3 客戶端安裝核心依賴
    • 4. 使用ModelScope下載大模型
    • 5. 方式一:構建自定義API服務
      • 5.1 接口文檔
        • 1. POST `/api/generate` - 生成文本(流式/非流式)
          • 接口說明
          • 請求體參數
          • 響應格式(流式)
          • 響應格式(非流式)
        • 2. GET `/api/tags` - 列出本地模型
          • 接口說明
          • 響應示例
          • 字段說明
        • 3. GET `/` - 健康檢查接口
          • 接口說明
          • 響應示例
      • 5.2 創建API服務腳本
      • 5.3 啟動API服務
    • 6. 方式二:使用Ollama一鍵部署大模型
      • 6.1 安裝Ollama
      • 6.2 拉取并運行模型
      • 6.3 啟動API服務
      • 6.4 Ollama API常用接口
        • 1. POST `/api/generate` - 生成文本(流式、非流式)
          • 功能說明
          • 請求詳情
          • 請求體參數
          • 響應格式(非流式)
          • 響應格式(流式)
          • 錯誤響應
        • 2. GET `/api/tags` - 列出本地模型
          • 功能說明
          • 請求詳情
          • 響應格式
          • 使用場景
        • 3.示例調用
    • 7. 客戶端開發
      • 7.1 獲取服務器IP地址
      • 7.2 客戶端主程序
      • 7.3 創建并加載索引服務
      • 7.4 客戶端運行截圖
    • 8. 常見問題與優化建議
      • 性能優化建議
    • 9. 總結與擴展
      • 兩種方式對比
      • 擴展方向

1. 硬件與系統準備

1.1 服務端要求

大模型推理對計算資源要求極高,尤其是顯存(VRAM)。

組件最低要求推薦配置說明
CPU8核16核以上(如Intel i9 / AMD Ryzen 9)多核可加速預處理
內存32GB64GB或更高模型加載和緩存需要大量內存
GPUNVIDIA GPU,顯存 ≥ 12GB(如RTX 3080)24GB+(如RTX 4090、A100、A6000)必須支持CUDA,顯存越大支持的模型越大
存儲100GB SSD500GB NVMe SSD模型文件較大,7B模型約15GB,70B可達140GB+
操作系統Ubuntu 20.04+ / Windows 10+ / macOSUbuntu 22.04 LTS推薦Linux,兼容性更好
CUDA & cuDNNCUDA 11.8+CUDA 12.1+必須安裝以啟用GPU加速

模型與顯存對照表(INT4量化后)

  • 7B模型:約6-8GB顯存
  • 13B模型:約10-14GB顯存
  • 70B模型:需多卡或CPU推理

1.2 客戶端要求

客戶端僅發送HTTP請求,資源要求極低。

組件要求說明
CPU雙核無特殊要求
內存4GB瀏覽器或腳本運行
網絡穩定連接訪問服務端IP:端口
工具瀏覽器、curl、Postman、Python腳本任意HTTP客戶端

? 結論:服務端需高性能機器,客戶端可為普通PC或手機。


2. 安裝Python環境與包管理工具 uv

uv 是由 Astral 開發的超快 Python 包安裝器和虛擬環境管理工具,性能遠超 pipconda

2.1 安裝 Python 3.10+

確保系統已安裝 Python 3.10 或更高版本:

python --version
# 輸出示例:Python 3.10.12

2.2 安裝 uv

# 下載并安裝 uv(支持 Linux/macOS/Windows)
curl -LsSf https://astral.sh/uv/install.sh | sh

驗證安裝:

uv --version
# 輸出示例:uv 0.2.8

? uv 支持:包安裝、虛擬環境管理、依賴解析、腳本運行,是 pip + venv + pip-tools 的替代品。


3. 創建虛擬環境并安裝依賴

使用 uv 創建隔離環境,避免依賴沖突。

3.1 創建虛擬環境

# 創建名為 .venv 的虛擬環境
uv venv .venv# 激活虛擬環境
source .venv/bin/activate

激活后,命令行前綴會顯示 (.venv)

3.2 服務器端安裝核心依賴

# 升級 pip
uv pip install --upgrade pip# 安裝大模型相關庫
uv pip install \torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121  # CUDA 12.1uv pip install \transformers \accelerate \      # 支持多GPU/混合精度fastapi \         # Web框架uvicorn \         # ASGI服務器pydantic \        # 數據校驗modelscope        # 魔搭模型下載

3.3 客戶端安裝核心依賴

# 升級 pip
uv pip install --upgrade pip# 安裝相關庫
uv pip install llama-index-core llama-index-llms-ollama llama-index-embeddings-ollama llama-index-vector-stores-faiss faiss-cpu

4. 使用ModelScope下載大模型

ModelScope(魔搭) 是阿里開源的模型開放平臺,提供大量中文優化模型。

下載模型:

uv run modelscope download --model ZhipuAI/GLM-4-9B-0414

在這里插入圖片描述

? 下載時間取決于網絡速度(9B模型約19GB,可能需數分鐘至數十分鐘)。


5. 方式一:構建自定義API服務

本服務基于 FastAPI 搭建,使用 transformers 加載 GLM-4-9B-0414 大模型,提供與 Ollama 兼容的 API 接口,支持流式和非流式文本生成。

  • 框架:FastAPI
  • 模型:ZhipuAI/GLM-4-9B-0414
  • 設備:自動檢測(CUDA / CPU)
  • 精度:FP16(CUDA),BF16(CPU)
  • 流式支持:? 支持 application/x-ndjson 流式輸出
  • 兼容性:兼容 Ollama 客戶端調用

5.1 接口文檔

1. POST /api/generate - 生成文本(流式/非流式)
接口說明

生成文本的核心接口,支持流式(默認)和非流式(raw=true)兩種模式。

屬性
路徑/api/generate
方法POST
內容類型application/json
流式響應? 支持
響應類型application/x-ndjson(流式) 或 application/json(非流式)
請求體參數
{"prompt": "用戶輸入的提示詞","messages": [{"role": "system", "content": "系統提示"},{"role": "user", "content": "用戶消息"},{"role": "assistant", "content": "助手回復"}],"options": {"temperature": 0.7,"top_p": 0.9,"repeat_penalty": 1.1},"raw": false
}
字段類型必填默認值說明
promptstring""純文本提示詞。若 messages 為空,則自動構造為 {"role": "user", "content": prompt}
messagesarray[object][]聊天消息數組,格式為 {"role": "user/system/assistant", "content": "..."}。優先級高于 prompt
optionsobject{}生成參數對象
options.temperaturenumber0.7溫度,控制生成隨機性(0.0 ~ 2.0)
options.top_pnumber0.9核采樣(Nucleus Sampling),控制多樣性(0.0 ~ 1.0)
options.repeat_penaltynumber1.1重復懲罰系數(>1.0 可減少重復)
rawbooleanfalse是否返回原始非流式響應。true:等待生成完成返回完整結果;false:流式逐 token 返回

?? 注意promptmessages 至少提供一個。

響應格式(流式)

raw=false(默認)時,返回 NDJSON(Newline Delimited JSON) 流:

{"model":"glm-4-9b","response":"你","done":false,"done_reason":null,"context":[]}
{"model":"glm-4-9b","response":"好","done":false,"done_reason":null,"context":[]}
{"model":"glm-4-9b","response":"!","done":false,"done_reason":null,"context":[]}
{"model":"GLM-4-9B-0414","response":"","done":true,"context":[]}
響應格式(非流式)

raw=true 時,返回完整 JSON 對象:

{"model": "GLM-4-9B-0414","response": "你好!我是GLM-4,由智譜AI研發的大語言模型...","done": true,"context": []
}

2. GET /api/tags - 列出本地模型
接口說明

返回當前服務支持的模型列表,兼容 Ollama 客戶端查詢模型列表。

屬性
路徑/api/tags
方法GET
響應類型application/json
響應示例
{"models": [{"name": "GLM-4-9B-0414","modified_at": "2025-04-14T00:00:00Z","size": 9000000000,"digest": "sha256:dummyglm49b","details": {"parent_model": "","format": "gguf","family": "glm","families": null,"parameter_size": "9B","quantization": "Q5_K_M"}}]
}
字段說明
字段類型說明
namestring模型名稱
modified_atstring模型最后修改時間(ISO 8601)
sizenumber模型文件大小(字節)
digeststring模型哈希值(此處為占位符)
details.formatstring模型格式(如 gguf、safetensors)
details.familystring模型家族(如 glm、llama、qwen)
details.parameter_sizestring參數規模(如 9B、30B)
details.quantizationstring量化方式(如 Q5_K_M)

📌 此接口用于客戶端發現可用模型,實際僅加載一個模型。


3. GET / - 健康檢查接口
接口說明

用于檢查服務是否正常運行。

屬性
路徑/
方法GET
響應類型application/json
響應示例
{"status": "running","model": "GLM-4-9B-0414"
}
字段類型說明
statusstring服務狀態,running 表示正常
modelstring當前加載的模型名稱

🔍 此接口不包含在 OpenAPI 文檔中(include_in_schema=False)。

5.2 創建API服務腳本

創建 api_server.py

# app.py
import os
import json
import re
import ast
from typing import Dict, List, Optional
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
from threading import Thread
from queue import Queue, Emptyapp = FastAPI()# ================== 配置 ==================
MODEL_PATH = "/home/db/Documents/LLM_ws/models/ZhipuAI/GLM-4-9B-0414"
# MODEL_PATH = "/home/db/Documents/LLM_ws/models/Qwen/Qwen3-30B-A3B-Instruct-2507-FP8"
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using device: {DEVICE}")tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(MODEL_PATH,device_map="auto",trust_remote_code=True,dtype=torch.float16 if DEVICE == "cuda" else torch.bfloat16,
)# ================== 流式生成器 ==================
def generate_stream(messages, temperature=0.7, top_p=0.9, repeat_penalty=1.1, max_new_tokens=1024):# 構造輸入prompt_messages = messagesinputs = tokenizer.apply_chat_template(prompt_messages,return_tensors="pt",add_generation_prompt=True,return_dict=True,).to(model.device)# 參數generate_kwargs = {"input_ids": inputs["input_ids"],"attention_mask": inputs["attention_mask"],"max_new_tokens": max_new_tokens,"temperature": temperature,"top_p": top_p,"repetition_penalty": repeat_penalty,"do_sample": True,"eos_token_id": tokenizer.eos_token_id,"pad_token_id": tokenizer.pad_token_id,}# 開始生成streamer_queue = Queue()def token_generator():try:outputs = model.generate(**generate_kwargs)output_ids = outputs[0][inputs["input_ids"].shape[1]:]text = tokenizer.decode(output_ids, skip_special_tokens=True)# 按 token 流式輸出for token in text:streamer_queue.put(token)streamer_queue.put(None)  # 結束標志except Exception as e:streamer_queue.put(f"Error: {str(e)}")streamer_queue.put(None)Thread(target=token_generator, daemon=True).start()buffer = ""assistant_content = ""while True:try:token = streamer_queue.get(timeout=60)if token is None:breakbuffer += tokenif buffer.strip():yield json.dumps({"model": "glm-4-9b","response": token,"done": False,"done_reason": None,"context": []}, ensure_ascii=False) + "\n"assistant_content += tokenexcept Empty:yield json.dumps({"error": "Stream timeout"}) + "\n"break# 最終完成yield json.dumps({"model": "GLM-4-9B-0414","response": "","done": True,"context": []}, ensure_ascii=False) + "\n"# ================== Ollama 兼容接口 ==================
@app.post("/api/generate")
async def generate(request: Request):body = await request.json()prompt = body.get("prompt", "")messages = body.get("messages", [])temperature = body.get("options", {}).get("temperature", 0.7)top_p = body.get("options", {}).get("top_p", 0.9)repeat_penalty = body.get("options", {}).get("repeat_penalty", 1.1)raw = body.get("raw", False)# 如果沒有 messages,嘗試從 prompt 構造if not messages and prompt:messages = [{"role": "user", "content": prompt}]if not raw:return StreamingResponse(generate_stream(messages, temperature, top_p, repeat_penalty),media_type="application/x-ndjson")else:# 非流式:收集所有輸出full_response = ""async for chunk in generate_stream(messages, temperature, top_p, repeat_penalty):data = json.loads(chunk)if "response" in data and data["done"] is False:full_response += data["response"]return {"model": "GLM-4-9B-0414","response": full_response,"done": True,"context": []}# ================== 可用模型 ==================
@app.get("/api/tags")
def api_tags():return {"models": [{"name": "GLM-4-9B-0414","modified_at": "2025-04-14T00:00:00Z","size": 9000000000,  # ~9GB FP16"digest": "sha256:dummyglm49b","details": {"parent_model": "","format": "gguf","family": "glm","families": None,"parameter_size": "9B","quantization": "Q5_K_M"}}]}# ================== 健康檢查 ==================
# 健康檢查接口
@app.get("/", include_in_schema=False)
async def health_check():return {"status": "running", "model": "GLM-4-9B-0414"}

5.3 啟動API服務

# 確保虛擬環境已激活
source .venv/bin/activate# 運行服務
uv run uvicorn api_server:app --host 0.0.0.0 --port 8080

在這里插入圖片描述

服務啟動后,在服務器訪問:http://localhost:8000,或在客戶端訪問:http://196.128.1.5:8000,若顯示{"status": "running", "model": "GLM-4-9B-0414"},表明服務正常。

在這里插入圖片描述


6. 方式二:使用Ollama一鍵部署大模型

6.1 安裝Ollama

Ollama 是最簡單的本地大模型運行工具。

# Linux/macOS
curl -fsSL https://ollama.com/install.sh | sh# Windows
# 下載安裝包:https://ollama.com/download/OllamaSetup.exe

驗證:

ollama --version
# 輸出示例:ollama version 0.1.43

6.2 拉取并運行模型

# 拉取Qwen3:32B
ollama pull qwen3:32b# 運行模型(交互模式)
ollama run qwen3:32b

輸入文本即可對話:

>>> 你好,請介紹一下你自己
我是通義千問,阿里巴巴集團旗下的通義實驗室自主研發的超大規模語言模型...

6.3 啟動API服務

Ollama 自帶API服務(默認 http://localhost:11434)。

# 啟動服務(后臺運行)
ollama serve  # 通常自動運行# 調用API生成文本
curl http://localhost:11434/api/generate -d '{"model": "qwen2:7b-instruct","prompt": "請寫一首關于秋天的詩","stream": false
}'

6.4 Ollama API常用接口

對于詳細的 Ollama API 接口,請參閱官方文檔Ollama 中文API文檔。

1. POST /api/generate - 生成文本(流式、非流式)
功能說明

向指定的大語言模型發送提示詞(prompt),并獲取模型生成的響應文本。支持流式非流式兩種響應模式。

此接口是 Ollama 的核心推理接口,適用于問答、文本生成、代碼補全等場景。


請求詳情
屬性
端點POST /api/generate
內容類型application/json
認證無需認證(本地服務)
流式支持? 支持 (stream=true)

請求體參數
{"model": "llama3","prompt": "請解釋量子計算的基本原理。","stream": false,"options": {"temperature": 0.7,"max_tokens": 512,"top_p": 0.9,"repeat_penalty": 1.1}
}
字段類型必填默認值說明
modelstring?-要使用的模型名稱(如 llama3, qwen:7b, mistral)。必須是已通過 ollama pull <model> 下載的模型。
promptstring?-輸入的提示詞或問題。模型將基于此內容生成響應。
streambooleanfalse是否啟用流式響應:
? true:逐 token 返回(NDJSON 格式)
? false:等待生成完成,返回完整結果
optionsobject{}可選的生成參數配置對象。
options.temperaturenumber0.8控制生成文本的隨機性。值越高越隨機(0.0 ~ 2.0)。
options.max_tokensnumber128生成的最大 token 數量。超過此長度將停止生成。
options.top_pnumber0.9核采樣(Nucleus Sampling)閾值,控制生成多樣性(0.0 ~ 1.0)。
options.repeat_penaltynumber1.1重復懲罰系數,防止模型重復輸出相同內容(>1.0 有效)。

?? 注意

  • prompt 字段不能為 null 或空字符串。
  • 若模型未下載,將返回 404 錯誤。

響應格式(非流式)

stream=false 時,返回一個完整的 JSON 對象:

{"model": "llama3","response": "量子計算是一種利用量子力學原理進行信息處理的計算方式...","done": true,"done_reason": "stop","context": [123, 456, 789],"total_duration": 1234567890,"load_duration": 987654321,"prompt_eval_count": 15,"prompt_eval_duration": 123456789,"eval_count": 256,"eval_duration": 987654321
}
字段類型說明
modelstring實際使用的模型名稱。
responsestring模型生成的完整文本。
doneboolean是否生成完成。true 表示結束。
done_reasonstring完成原因:stop(正常結束)、length(達到最大 token 數)。
contextarray<number>上下文 token IDs,可用于后續對話的 context 字段以保持上下文連貫。
total_durationnumber總耗時(納秒)。
load_durationnumber模型加載耗時(納秒)。
prompt_eval_countnumber提示詞評估的 token 數。
prompt_eval_durationnumber提示詞處理耗時(納秒)。
eval_countnumber生成的 token 數。
eval_durationnumber生成耗時(納秒)。

響應格式(流式)

stream=true 時,返回 NDJSON(Newline Delimited JSON) 流,每行一個 JSON 對象:

{"model":"llama3","response":"量子","done":false}
{"model":"llama3","response":"計算","done":false}
{"model":"llama3","response":"是一","done":false}
{"model":"llama3","response":"種","done":false}
{"model":"llama3","response":"利用","done":false}
{"model":"llama3","response":"量子","done":false}
{"model":"llama3","response":"力學","done":false}
{"model":"llama3","response":"原理","done":false}
{"model":"llama3","response":"進行","done":false}
{"model":"llama3","response":"信息","done":false}
{"model":"llama3","response":"處理","done":false}
{"model":"llama3","response":"的","done":false}
{"model":"llama3","response":"計算","done":false}
{"model":"llama3","response":"方式","done":false}
{"model":"llama3","response":"...","done":true,"context":[123,456,789],"total_duration":1234567890,"load_duration":987654321,"prompt_eval_count":15,"prompt_eval_duration":123456789,"eval_count":256,"eval_duration":987654321}
  • done: false:表示生成中,response 為新生成的 token。
  • done: true:表示生成完成,包含完整統計信息。

🌐 流式響應適用于 Web 應用實現“打字機”效果。


錯誤響應
狀態碼錯誤示例說明
400{"error": "model is required"}請求參數缺失或格式錯誤
404{"error": "model 'xxx' not found"}指定模型未下載
500{"error": "failed to initialize model"}模型加載失敗(如顯存不足)

2. GET /api/tags - 列出本地模型
功能說明

獲取當前本地已下載并可用的所有模型列表。用于客戶端(如 Web UI、CLI 工具)展示可用模型。


請求詳情
屬性
端點GET /api/tags
認證無需認證
響應類型application/json

響應格式
{"models": [{"name": "llama3:8b","size": 4718592000,"digest": "sha256:abc123...","details": {"parent_model": "","format": "gguf","family": "llama","families": ["llama", "transformer"],"parameter_size": "8B","quantization": "Q4_K_M"},"modified_at": "2025-08-20T10:30:00.123Z"},{"name": "qwen:7b","size": 3984588800,"digest": "sha256:def456...","details": {"parent_model": "","format": "gguf","family": "qwen","families": ["qwen", "transformer"],"parameter_size": "7B","quantization": "Q5_K_S"},"modified_at": "2025-08-15T14:20:00.456Z"}]
}
字段類型說明
modelsarray<object>模型列表數組。
models[].namestring模型名稱,可能包含標簽(如 :7b, :latest)。
models[].sizenumber模型文件總大小(字節)。
models[].digeststring模型內容的 SHA256 哈希值,用于唯一標識。
models[].modified_atstring模型最后修改時間(ISO 8601 UTC 格式)。
models[].detailsobject模型詳細信息(可選)。
models[].details.parent_modelstring父模型名稱(用于微調模型)。
models[].details.formatstring模型格式(如 gguf)。
models[].details.familystring模型家族(如 llama, qwen, mistral)。
models[].details.familiesarray<string>模型所屬的所有家族。
models[].details.parameter_sizestring參數規模(如 7B, 13B)。
models[].details.quantizationstring量化級別(如 Q4_K_M, Q5_K_S)。

使用場景
  • 啟動時加載模型列表
  • 用戶選擇模型下拉框
  • 模型管理界面

3.示例調用
# 列出所有模型
curl http://localhost:11434/api/tags# 生成文本(非流式)
curl http://localhost:11434/api/generate -d '{"model": "llama3","prompt": "你好","stream": false
}'# 生成文本(流式)
curl http://localhost:11434/api/generate -d '{"model": "qwen:7b","prompt": "請寫一首詩","stream": true
}' --no-buffer

7. 客戶端開發

7.1 獲取服務器IP地址

在服務器上執行以下命令:

ip addr show

輸出當前系統的所有網絡接口及其配置信息,例如:
在這里插入圖片描述

其中,lo接口為本地回環接口,enp4s0接口為有線網絡接口,Meta接口為虛擬或隧道接口。在enp4s0接口中,inet 192.168.1.5/24即為IPv4地址,是服務器的局域網IP,/24表示子網掩碼255.255.255.0,同一局域網中,IP范圍是192.168.1.1192.168.1.254。因此,服務器IP地址為192.168.1.5/24

7.2 客戶端主程序

創建 client.py

import streamlit as st
import requests
import json
import base64
import os
from index import KnowledgeBaseManager
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.core import QueryBundle
import shutil# 設置頁面配置
st.set_page_config(page_title="AI智能問答助手",page_icon="🤖",layout="wide"
)# 標題和描述
st.title("💬 AI智能問答助手")# 初始化會話狀態中的配置
if 'ollama_host' not in st.session_state:st.session_state.ollama_host = "192.168.1.5"
if 'ollama_port' not in st.session_state:st.session_state.ollama_port = "11434"# Ollama 服務器配置(從會話狀態獲取)
OLLAMA_HOST = f"http://{st.session_state.ollama_host}:{st.session_state.ollama_port}"# 緩存 kb_manager
@st.cache_resource
def get_kb_manager(kb_root, ollama_host=OLLAMA_HOST):return KnowledgeBaseManager(kb_root=kb_root)kb_manager = get_kb_manager(kb_root=r".\data")
# 知識庫目錄
kb_dir = kb_manager.get_kb_path("my_kb")# 檢查 Ollama 服務器是否可達
# @st.cache_resource(ttl=10)  # 每10秒刷新一次連接狀態
def check_ollama_connection(host, port):try:response = requests.get(f"http://{host}:{port}/api/tags", timeout=5)return response.status_code == 200except:return False# 將多行文本轉換為 Markdown 引用塊(每行都加 >)
def format_as_quote(text):"""將文本格式化為 Markdown 引用塊,每行都以 > 開頭"""lines = text.strip().split('\n')quoted_lines = [f"> {line.strip()}  " for line in lines if line.strip()]return '\n'.join(quoted_lines)# --- 圖片轉 base64 ---
def get_image_base64(image_file):image_file.seek(0)bytes_data = image_file.read()return base64.b64encode(bytes_data).decode('utf-8')# 流式調用 Ollama API
def stream_query_ollama(prompt, model="qwen3:30b", image_base64=None):try:url = f"{OLLAMA_HOST}/api/generate"# 構造 optionsoptions = {"temperature": st.session_state.get("temperature", 0.7),"top_p": st.session_state.get("top_p", 0.9),"repeat_penalty": st.session_state.get("repeat_penalty", 1.1),}if not show_thinking:options["raw"] = Trueelse:options["raw"] = Falsepayload = {"model": model,"prompt": prompt,"stream": True,"options": options}# 如果是多模態模型且有圖片if image_base64 and model in ["llama4:latest", "blaifa/InternVL3_5:8b", "gemma3:27b"]:payload["images"] = [image_base64]response = requests.post(url, json=payload, timeout=120, stream=True)if response.status_code != 200:error_msg = f"請求失敗: {response.status_code} - {response.text}"st.error(error_msg)return error_msg, ""# 創建一個占位符,用于動態更新內容message_placeholder = st.empty()full_response = ""thinking_content = ""in_thinking = False  # 標記是否在 <think> 標簽內try:for line in response.iter_lines():if not line:continuetry:body = json.loads(line.decode('utf-8'))if 'response' not in body:continuecontent = body['response']# 處理 <think> 標簽if '<think>' in content:in_thinking = Truethinking_content += content.replace('<think>', '')# 實時更新思考過程display_content = ""if thinking_content.strip():display_content += f"{format_as_quote('【思考過程】: ' + thinking_content)}\n\n"if full_response:display_content += full_responsemessage_placeholder.markdown(display_content)elif '</think>' in content:in_thinking = Falsethinking_content += content.replace('</think>', '')# 實時更新思考過程display_content = ""if thinking_content.strip():display_content += f"{format_as_quote('【思考過程】: ' + thinking_content)}\n\n"if full_response:display_content += full_responsemessage_placeholder.markdown(display_content)elif in_thinking:thinking_content += content# 實時更新思考過程display_content = ""if thinking_content.strip():display_content += f"{format_as_quote('【思考過程】: ' + thinking_content)}\n\n"if full_response:display_content += full_responsemessage_placeholder.markdown(display_content)else:full_response += content# 實時更新主響應display_content = ""if thinking_content.strip():display_content += f"{format_as_quote('【思考過程】: ' + thinking_content)}\n\n"display_content += full_responsemessage_placeholder.markdown(display_content)except json.JSONDecodeError:continueexcept Exception as e:st.error(f"流式解析錯誤: {str(e)}")return full_response.strip(), thinking_content.strip()except requests.exceptions.RequestException as e:error_msg = f"連接錯誤: {str(e)}"st.error(error_msg)return error_msg, ""except Exception as e:error_msg = f"未知錯誤: {str(e)}"st.error(error_msg)return error_msg, ""# 非流式調用
def query_ollama(prompt, model="qwen3:30b", image_base64=None):"""非流式調用 Ollama API,假設 <think> 和 </think> 標簽一定存在返回: (full_response, thinking_content)"""try:url = f"{OLLAMA_HOST}/api/generate"# 構造 optionsoptions = {"temperature": st.session_state.get("temperature", 0.7),"top_p": st.session_state.get("top_p", 0.9),"repeat_penalty": st.session_state.get("repeat_penalty", 1.1),}if not show_thinking:options["raw"] = Trueelse:options["raw"] = Falsepayload = {"model": model,"prompt": prompt,"stream": False,"options": options}# 如果是多模態模型且有圖片if image_base64 and model in ["llama4:latest", "blaifa/InternVL3_5:8b", "gemma3:27b"]:payload["images"] = [image_base64]response = requests.post(url, json=payload, timeout=120)if response.status_code == 200:result = response.json()content = result.get("response", "")# 直接提取 <think> 標簽內的內容start_tag = "<think>"end_tag = "</think>"start_pos = content.find(start_tag)end_pos = content.find(end_tag)if start_pos != -1 and end_pos != -1 and start_pos < end_pos:thinking_content = content[start_pos + len(start_tag):end_pos].strip()full_response = content[end_pos + len(end_tag):].strip()else:# 如果標簽解析失敗,全部作為主響應thinking_content = ""full_response = content.strip()# 在這里統一更新 UIif thinking_content.strip() and show_thinking:st.markdown(format_as_quote('【思考過程】: ' + thinking_content))st.markdown(full_response)return full_response, thinking_contentelse:error_msg = f"請求失敗: {response.status_code} - {response.text}"st.error(error_msg)return error_msg, ""except requests.exceptions.RequestException as e:error_msg = f"連接錯誤: {str(e)}"st.error(error_msg)return error_msg, ""except Exception as e:error_msg = f"未知錯誤: {str(e)}"st.error(error_msg)return error_msg, ""# 側邊欄設置 - 可配置的IP和端口
with st.sidebar:st.header("🖥? 服務器配置")# IP地址和端口輸入(可編輯)new_host = st.text_input("IP地址", value=st.session_state.ollama_host)new_port = st.text_input("端口", value=st.session_state.ollama_port)# 保存配置按鈕if st.button("保存配置"):st.session_state.ollama_host = new_hostst.session_state.ollama_port = new_portst.rerun()  # 重新加載頁面以應用新配置# 獲取連接狀態
is_connected = check_ollama_connection(st.session_state.ollama_host, st.session_state.ollama_port)# 根據連接狀態顯示不同內容
if is_connected:st.success(f"? 已成功連接到服務器 ({st.session_state.ollama_host}:{st.session_state.ollama_port})")# 獲取可用模型列表try:models_response = requests.get(f"{OLLAMA_HOST}/api/tags")models_data = models_response.json()available_models = [model["name"] for model in models_data.get("models", [])]if not available_models:available_models = []  # 默認模型列表st.warning("?? 無法獲取模型列表,使用默認模型")except:available_models = []st.warning("?? 無法獲取模型列表,使用默認模型")# 模型選擇with st.sidebar:st.markdown("---")st.header("🤖 模型選擇")# 1. ? 生成模型(主 LLM)llm_models = [m for m in available_models if "text" not in m.lower() and "rerank" not in m.lower() and "embed" not in m.lower()]selected_model = st.selectbox("生成模型",options=llm_models,index=0,key="model_selector",help="用于生成最終回答的大語言模型,如 llama4、qwen3 等")# 2. ? Embedding 模型embedding_models = [m for m in available_models if "embed" in m.lower() or "text" in m.lower()]if embedding_models:selected_embedding = st.selectbox("📚 Embedding 模型",options=embedding_models,index=0,key="embedding_selector",help="用于將文檔轉換為向量,支持語義檢索。推薦:nomic-embed-text:latest")else:selected_embedding = "nomic-embed-text:latest"  # 默認回退st.info("?? 未檢測到 Embedding 模型,建議拉取:`ollama pull nomic-embed-text`", icon="💡")# 3. ?? Rerank 模型(可選)rerank_models = [m for m in available_models if "rerank" in m.lower()]use_rerank = st.checkbox("啟用 Rerank 模型", value=bool(rerank_models), key="use_rerank_toggle")if use_rerank and rerank_models:selected_rerank = st.selectbox("🔍 Rerank 模型",options=rerank_models,index=0,key="rerank_selector",help="用于對檢索結果重新排序,提升相關性。推薦:mxbai-rerank:large")elif use_rerank:selected_rerank = "mxbai-rerank:large"  # 默認st.info("?? 未檢測到 Rerank 模型,建議拉取:`ollama pull mxbai-rerank:large`", icon="💡")else:selected_rerank = None# 流式傳輸選項enable_streaming = st.checkbox("啟用流式傳輸", value=True, key="streaming_toggle")# 顯示思考內容選項show_thinking = st.checkbox("顯示模型思考過程", value=True, key="thinking_toggle")st.markdown("---")st.header("?? 模型參數")# Temperaturetemperature = st.slider("Temperature", min_value=0.0, max_value=2.0, value=0.7, step=0.1,help="控制生成文本的隨機性。值越高越隨機,越低越確定。")# top_ptop_p = st.slider("Top P", min_value=0.0, max_value=1.0, value=0.9, step=0.05,help="核采樣。控制從累積概率最高的詞匯中采樣。")# repeat_penaltyrepeat_penalty = st.slider("repeat_penalty", min_value=0.0, max_value=2.0, value=1.1, step=0.1,help="懲罰重復的 token,避免循環輸出。")st.markdown("---")st.markdown("### 📁 文件上傳")uploaded_image = st.file_uploader("📸 上傳圖片",type=["png", "jpg", "jpeg", "webp"],key="sidebar_image_uploader")if uploaded_image:st.image(uploaded_image,caption="已上傳圖片",width="stretch"  # ? 替代 use_container_width=True)# ? 文件上傳(放在這里)uploaded_file = st.file_uploader("📄 上傳文檔(PDF/TXT/DOCX)",type=["pdf", "txt", "docx"],accept_multiple_files=False,key="sidebar_file_uploader")if uploaded_file:st.success(f"📎 {uploaded_file.name}", icon="?")if not ('current_index' in st.session_state and 'current_file' in st.session_state and st.session_state.current_file == uploaded_file.name):# ?【新增】立即處理文件:保存 + 構建索引file_dir = os.path.join(kb_dir, "files")file_path = os.path.join(file_dir, uploaded_file.name)# 創建目錄os.makedirs(file_dir, exist_ok=True)# 清空 files 目錄for item in os.listdir(file_dir):item_path = os.path.join(file_dir, item)try:if os.path.isfile(item_path) or os.path.islink(item_path):os.unlink(item_path)elif os.path.isdir(item_path):shutil.rmtree(item_path)except Exception as e:st.warning(f"?? 刪除失敗: {item_path}, 原因: {e}")# 保存新文件try:with open(file_path, "wb") as f:f.write(uploaded_file.getbuffer())# ? 構建索引(異步或同步)with st.spinner("正在構建知識庫索引..."):my_index = kb_manager.build_index(kb_dir, selected_model=selected_model, selected_embedding=selected_embedding)st.success("? 知識庫索引已更新!", icon="🧠")# ? 可選:緩存索引到 session_statest.session_state['current_index'] = my_indexst.session_state['current_file'] = uploaded_file.nameexcept Exception as e:st.error(f"? 文件保存或索引構建失敗: {e}")# 可選:添加說明st.caption("上傳的文件將作為上下文參與對話。")else:st.error("? 無法連接到服務器,請檢查配置:")st.markdown("""- 服務器 IP 地址是否正確- 端口是否正確- 服務是否正在運行- 網絡連接是否正常- 防火墻設置是否允許連接""")# 禁用模型選擇等控件selected_model = "qwen3:30b"enable_streaming = Falseshow_thinking = False# 初始化聊天歷史
if 'messages' not in st.session_state:st.session_state.messages = []if len(st.session_state.messages) == 0:st.session_state.messages = [{"role": "assistant", "content": "您好!我是您的智能助手,請問有什么可以幫助您的?", "thinking": "", "table_data": []}]# 顯示聊天歷史
for message in st.session_state.messages:with st.chat_message(message["role"]):# 顯示思考內容(如果存在且用戶選擇顯示)if message["thinking"] and show_thinking:st.markdown(format_as_quote('【思考過程】: ' + message['thinking']))# 顯示主要回復內容st.markdown(message["content"])# 顯示檢索結果if len(message["table_data"]) != 0:st.dataframe(message["table_data"], width='content')# 用戶輸入界面
if is_connected:  # 只有在連接成功時才顯示輸入框prompt = st.chat_input("輸入您的問題...", key="chat_input")if prompt:# 添加用戶消息到歷史st.session_state.messages.append({"role": "user", "content": prompt, "thinking": "", "table_data": []})# 顯示用戶消息with st.chat_message("user"):st.markdown(prompt)# 🖼? 顯示當前使用的圖片if uploaded_image and selected_model in ["llama4:latest", "blaifa/InternVL3_5:8b", "gemma3:27b"]:st.image(uploaded_image, width=120)# 📎 顯示當前使用的文件if uploaded_file:st.markdown(f"📌 當前會話使用文件: `{uploaded_file.name}`")# --- 構造上下文 ---final_prompt = promptimage_base64 = None# 獲取圖片 base64if uploaded_image:image_base64 = get_image_base64(uploaded_image)# 添加文檔內容if uploaded_file:# ? 使用已構建的索引(來自 session_state 或直接加載)if 'current_index' in st.session_state:my_index = st.session_state['current_index']else:my_index = kb_manager.load_index(kb_dir, selected_model=selected_model, selected_embedding=selected_embedding)  # 兜底# 創建檢索器retriever = VectorIndexRetriever(index=my_index,similarity_top_k=5,  # 檢索最相關的5個文檔片段)query_bundle = QueryBundle(query_str=prompt)retrieved_nodes = retriever.retrieve(query_bundle)# # 選出retrieved_nodes中score高于60%的# retrieved_nodes = [node for node in retrieved_nodes if node.score > 0.6]content = "\n".join([n.get_content() for n in retrieved_nodes])final_prompt = f"請結合以下知識片段回答問題:\n\n{content}\n\n問題:{prompt}\n\n回答:"# 顯示助手響應with st.chat_message("assistant"):with st.spinner("正在思考..."):if enable_streaming:# 使用流式傳輸full_response, thinking_content = stream_query_ollama(final_prompt, selected_model, image_base64=image_base64)else:# 使用非流式傳輸full_response, thinking_content = query_ollama(final_prompt, selected_model, image_base64=image_base64)# ?【新增】展示 Top5 檢索結果if uploaded_file and 'retrieved_nodes' in locals():st.markdown("---")  # 分隔線st.markdown("#### 🔍 檢索到的相關文本塊(Top 5)")# 構造表格數據table_data = []for i, node in enumerate(retrieved_nodes):table_data.append({"排名": i + 1,"相似度": f"{node.score:.4f}" if node.score is not None else "N/A","文件名": node.metadata.get("file_name", "未知文件"),"文本片段": node.get_content()})st.dataframe(table_data, width='content')# 將響應添加到歷史st.session_state.messages.append({"role": "assistant", "content": full_response, "thinking": thinking_content if show_thinking else "","table_data": []})# 添加新對話按鈕
if st.button("🔄 新對話"):st.session_state.messages = [{"role": "assistant", "content": "您好!我是您的智能助手,請問有什么可以幫助您的?", "thinking": "", "table_data": []}]st.rerun()

7.3 創建并加載索引服務

創建 index.py

import os
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext, load_index_from_storage
from llama_index.llms.ollama import Ollama  # 使用 Ollama LLM
from llama_index.embeddings.ollama import OllamaEmbedding  # 使用 Ollama Embedding
from llama_index.vector_stores.faiss import FaissVectorStore
from llama_index.core.node_parser import SentenceSplitter
import faiss
import warningswarnings.filterwarnings("ignore", category=UserWarning, message="TypedStorage is deprecated")class KnowledgeBaseManager:def __init__(self, kb_root=".\data", ollama_host="http://localhost:11434"):self.kb_root = kb_rootself.ollama_host = ollama_hostos.makedirs(self.kb_root, exist_ok=True)def get_kb_path(self, folder_name):return os.path.join(self.kb_root, folder_name)def build_index(self, kb_dir, selected_model="gemma3:27b", selected_embedding="nomic-embed-text:latest"):file_dir = os.path.join(kb_dir, "files")vector_dir = os.path.join(kb_dir, "vectors")os.makedirs(file_dir, exist_ok=True)os.makedirs(vector_dir, exist_ok=True)# 初始化 Ollama LLM(用于生成)llm = Ollama(model=selected_model,base_url=self.ollama_host,request_timeout=120.0,)# 初始化 Ollama Embedding 模型(用于向量化)embed_model = OllamaEmbedding(model_name=selected_embedding,base_url=self.ollama_host,ollama_additional_kwargs={"keep_alive": "5m"}  # 可選:保持模型在內存中)# 加載文檔documents = SimpleDirectoryReader(file_dir).load_data()if not documents:raise ValueError(f"在 {file_dir} 中未找到文檔")# 文檔切片text_splitter = SentenceSplitter(chunk_size=2500,chunk_overlap=500,separator="\n")nodes = text_splitter.get_nodes_from_documents(documents)# 獲取嵌入維度(自動)sample_embedding = embed_model.get_text_embedding("樣本文本")d = len(sample_embedding)  # 自動獲取維度(如 nomic-embed-text 是 768)# 創建 Faiss 索引faiss_index = faiss.IndexFlatL2(d)vector_store = FaissVectorStore(faiss_index=faiss_index)# 構建索引index = VectorStoreIndex(nodes,llm=llm,vector_store=vector_store,embed_model=embed_model,show_progress=True)# 保存向量索引index.storage_context.persist(persist_dir=vector_dir)return indexdef load_index(self, kb_dir, selected_model="gemma3:27b", selected_embedding="nomic-embed-text:latest"):vector_dir = os.path.join(kb_dir, "vectors")if not os.path.exists(vector_dir):raise ValueError(f"向量目錄不存在: {vector_dir}")# 初始化 Ollama LLM(用于生成)llm = Ollama(model=selected_model,base_url=self.ollama_host,request_timeout=120.0,)# 初始化 Ollama Embedding 模型(用于向量化)embed_model = OllamaEmbedding(model_name=selected_embedding,base_url=self.ollama_host,ollama_additional_kwargs={"keep_alive": "5m"}  # 可選:保持模型在內存中)storage_context = StorageContext.from_defaults(persist_dir=vector_dir)index = load_index_from_storage(storage_context,llm=llm,embed_model=embed_model,show_progress=True)return index

7.4 客戶端運行截圖

初始化頁面:

在這里插入圖片描述智能問答(支持流式傳輸、深度思考):在這里插入圖片描述

模型理解:在這里插入圖片描述RAG(檢索增強生成,支持TXT、Docx、PDF):在這里插入圖片描述樣本_XK20_zh.pdf(部分):在這里插入圖片描述


8. 常見問題與優化建議

問題解決方案
CUDA out of memory使用 device_map="auto"torch_dtype=torch.float16、或量化(如bitsandbytes)
模型下載慢使用國內鏡像或 modelscopemirror_url 參數
Ollama無法啟動檢查端口11434是否被占用,重啟服務
API響應慢升級GPU、使用更小模型、啟用Flash Attention
中文亂碼確保分詞器支持中文,設置 tokenizer.encode(..., add_special_tokens=True)

性能優化建議

  • 使用模型量化:bitsandbytes(4-bit/8-bit)
  • 多GPU部署:device_map="balanced_low_0"

9. 總結與擴展

兩種方式對比

項目自建API服務Ollama
難度中等極低
靈活性高(可定制)
維護成本
適合場景生產環境、企業級應用快速原型、個人使用

擴展方向

  • 添加API密鑰鑒權(JWT)
  • 部署前端界面(Gradio / Streamlit)
  • 使用Docker容器化
  • 集成向量數據庫(RAG)
  • 多模型路由(Model Router)
  • 支持多輪對話:(POST /api/chat)
  • 本地持久化歷史對話(數據庫)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/921064.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/921064.shtml
英文地址,請注明出處:http://en.pswp.cn/news/921064.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

亞馬遜美加站點物流新規解讀:庫存處理邏輯重構與賣家應對策略

2025年9月&#xff0c;亞馬遜美國與加拿大站點即將實施物流計劃強制調整&#xff0c;批量清貨與捐贈計劃的規則迭代&#xff0c;標志著平臺對庫存生命周期管理的重視程度提升&#xff0c;此次新規以“可持續發展”為核心導向&#xff0c;通過強制與默認參與的雙重機制&#xff…

SpringBoot Web 入門指南:從零搭建第一個SpringBoot程序

SpringBoot Web 入門指南&#xff1a;從零搭建第一個SpringBoot程序SpringBoot Web 入門指南&#xff1a;從零搭建第一個SpringBoot程序一、Web開發基礎&#xff1a;靜態/動態資源與B/S、C/S架構解析?資源類型系統架構二、Spring 與 Spring Boot 核心介紹1. Spring 框架2. Spr…

從圖靈完備性到現實差距:為什么你的設備和你本人都潛力無限,卻表現各異?

理論上的無限潛力&#xff0c;為何被困在現實的牢籠中&#xff1f;一、引言&#xff1a;一個反直覺的概念 在計算機科學中&#xff0c;圖靈完備性&#xff08;Turing Completeness&#xff09; 是衡量一個系統計算能力的黃金標準。它得名于計算機科學之父艾倫圖靈&#xff08;A…

Android系統打通HAL層到應用層 --- Framework框架搭建

本文是接續上文&#xff0c;針對于HAL層的接口封裝Framework層的接口 HAL層框架搭建&#xff1a;https://blog.csdn.net/m0_50408097/article/details/151148637?spm1001.2014.3001.5502 在 Android 系統架構中&#xff0c;Framework 層&#xff08;框架層&#xff09; 位于 H…

LwIP入門實戰 — 2 LwIP概述

目錄 2.1 LwIP簡介 2.2 LwIP文件架構分析 2.2.1 LwIP軟件架構 2.2.2 主要模塊劃分 2.3 IPC通訊機制 2.4 LwIP的3種編程接口 2.4.1 RAW/Callback API 2.4.2 Netconn API 2.1 LwIP簡介 LWIP&#xff08;Light Weight Internet Protocol&#xff0c;輕型網絡協議棧&#…

微信小程序-day3

頁面導航跳轉聲明式導航注意&#xff1a;url開頭要有/1. 導航到 tabBar 頁面2. 導航到非 tabBar 頁面3. 后退導航編程式導航跳轉傳參參數可以在onLoad里用option獲取下拉刷新事件可在onPullDownRefresh中定義下拉事件對應操作在其中加入這個函數wx.stopPullDownRefresh()&#…

關于ES中文分詞器analysis-ik快速安裝

ES中文分詞器插件 安裝快速安裝手動安裝 應用ik_max_word 與 ik_smart 的區別驗證是否生效 官方地址&#xff1a;https://github.com/infinilabs/analysis-ik 安裝 快速安裝 插件安裝&#xff08;將鏈接最后的版本號換成當前ES版本號&#xff09;&#xff1a; bin/elastics…

STM32G4 電流環閉環

目錄一、STM32G4 電流環閉環1 電流環閉環PID控制2 電流環閉環建模附學習參考網址歡迎大家有問題評論交流 (* ^ ω ^)一、STM32G4 電流環閉環 1 電流環閉環 電流環框圖 PID控制 時域和拉普拉斯域的傳遞函數 PID&#xff1a; P比例部分&#xff0c;I積分部分&#xff0c;D微分…

利用 Java 爬蟲獲取淘寶商品詳情 API 接口

本文將詳細介紹如何使用 Java 編寫爬蟲程序&#xff0c;通過淘寶開放平臺的高級版 API 接口獲取商品的詳細信息。一、淘寶商品詳情 API 接口概述淘寶開放平臺提供了多個 API 接口用于獲取商品的詳細信息&#xff0c;其中 taobao.item.get 和 taobao.item.get_pro 是常用的接口。…

idea上傳本地項目代碼到Gitee倉庫教程

前言&#xff1a;本地一個項目代碼上傳到Gitee倉庫1.登錄Gitee官網新建倉庫&#xff08;命名跟項目同名&#xff09;2.idea添加Gitee插件&#xff08;需要Restart&#xff09;3.idea配置已安裝git的路徑4.idea添加Gitee賬戶5.給項目創建Git本地倉庫Git倉庫創建成功&#xff0c;…

往屆生還有機會進入計算機這個行業嗎?還能找見好工作嗎

前言 最近有很多的往屆生來咨詢我&#xff0c;問我還能找見工作嗎&#xff0c;還能進入這一行嗎&#xff08;大多數都是一些24屆&#xff0c;考研失敗的同學&#xff09; 針對目前這種情況&#xff0c;還能不能進&#xff0c;只能說很難&#xff0c;非常難。 在這里&#xff0c…

Python爬蟲實戰:研究 Lines, bars and markers 模塊,構建電商平臺數據采集和分析系統

1. 引言 1.1 研究背景 隨著互聯網技術的飛速發展,網絡上積累了海量的數據資源,這些數據蘊含著豐富的信息和價值。如何高效地獲取、處理和分析這些數據,成為信息時代面臨的重要課題。Python 作為一種功能強大的編程語言,憑借其豐富的庫支持和簡潔的語法,在網絡數據爬取和…

大文件穩定上傳:Spring Boot + MinIO 斷點續傳實踐

文章目錄一、引言&#xff1a;問題背景二、技術選型與項目架構三、核心設計與實現1. 初始化上傳 (/init)2. 上傳分塊 (/chunk)3. 完成上傳與合并 (/complete)4. 查詢上傳進度 (/progress)四、斷點續傳工作流程五、方案優勢總結六、拓展優化七、方案優勢對比一、引言&#xff1a…

表達式語言EL

表達式語言EL 1.EL表達式的作用 可以說&#xff0c;EL&#xff08;Expression Language&#xff09;表達式語言&#xff0c;就是用來替代<% %>的&#xff0c;EL比<%%>更簡潔&#xff0c;更方便。 2.與請求參數有關的內置對象 1.使用表達式&#xff1a;<%request…

pycharm無法添加本地conda解釋器/命令行激活conda時出現很多無關內容

本文主要解決以下兩種問題&#xff1a;1.pycharm在添加本地非base環境時出現無法添加的情況&#xff0c;特征為&#xff1a;正在創建conda解釋器--->彈出一個黑窗口又迅速關閉&#xff0c;最終無法添加成功2.在conda prompt中進行activate 指定env&#xff08;非base&#x…

LeetCode 844.比較含退格的字符串

給定 s 和 t 兩個字符串&#xff0c;當它們分別被輸入到空白的文本編輯器后&#xff0c;如果兩者相等&#xff0c;返回 true 。# 代表退格字符。 注意&#xff1a;如果對空文本輸入退格字符&#xff0c;文本繼續為空。 示例 1&#xff1a; 輸入&#xff1a;s “ab#c”, t “a…

什么是涌浪電壓

涌浪電壓&#xff08;浪涌電壓&#xff09;是電路或設備在運行時突然出現的、超出額定電壓的瞬時過電壓。它通常由雷擊、電感性負載的斷開、電力系統的故障切換或大型電容性負載的接通等原因引起。涌浪電壓是一種高能量的瞬變干擾&#xff0c;可能損壞電子設備&#xff0c;如擊…

uniapp 優博訊k329藍牙打印機,設置打印機,一鍵打印

設置頁面&#xff1a;<template><view class"pageBg"><u-navbar leftIconColor"#fff" :leftIconSize"28" title"打印設置" bgColor"#3c9cff" :placeholder"true"leftClick"$navigateBack&quo…

pikachu之sql注入

目錄 XX型注入 insert/update注入 delete注入 "http header"注入 基于boolian的盲注 基于時間的盲注 寬字節注入&#xff08;wide byte注入&#xff09; pikachu靶場的字符型注入中xx or 11#可以得到所有用戶的信息。 XX型注入 首先輸入1探測一下。 然后返回…

TLS(傳輸層安全協議)

文章目錄一、核心概念二、為什么需要 TLS/SSL&#xff1f;三、工作原理與詳細流程握手步驟詳解&#xff1a;1.ClientHello & ServerHello&#xff1a;2.服務器認證 (Certificate, ServerKeyExchange)&#xff1a;3.客戶端響應 (ClientKeyExchange, Finished)&#xff1a;4.…