大模型本地部署与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.…