news 2026/10/16 10:08:51

基于LangChain与Ollama的本地私有知识库RAG问答系统实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于LangChain与Ollama的本地私有知识库RAG问答系统实战

1. 项目概述与核心需求解析

1.1 这个项目到底在解决什么问题

先聊一个很现实的问题:很多人手里攒了一堆文档——产品手册、技术方案、会议纪要、行业报告、甚至几百页的PDF教材,真正要用的时候却翻半天找不到关键信息。用通用搜索引擎吧,它不知道你企业内部的专业术语;问在线大模型吧,数据不能外传,答案也可能一本正经地胡说八道。我这次折腾的项目,就是想用一套完全本地化的方案,把“文档查询”这件事做成一个私有知识库,让大模型只基于我投喂的资料回答,不联网、不出内网、答案还能标注来源。

整个方案的技术栈很明确:LangChain做编排框架,Ollama跑本地大语言模型,向量数据库存文档切片,再加一个文档加载和检索链路,最终组合成一个RAG(Retrieval-Augmented Generation,检索增强生成)应用。关键词是“私有”“本地”“多源文档”,核心诉求就是数据不出本地、支持把PDF/Word/Markdown/网页等乱七八糟的格式统一变成可查询的知识。

1.2 为什么非要用RAG而不是微调

很多第一次接触这个方向的朋友会问:既然要让模型懂我的文档,为什么不直接微调一个模型?我个人的看法是,RAG和微调的适用场景完全不同。微调适合改变模型的行为模式和输出风格、固化某些专业领域知识,但成本高、周期长、更新麻烦。而RAG相当于给模型配了一本可以随时翻的参考书,每次回答都现场检索相关内容再组织答案。

对于“私有知识库查询”这个场景,RAG有四个碾压级优势:

  • 实时更新:往向量库里丢一个新文档,立刻就能被检索到,不需要重新训练。
  • 答案可溯源:能告诉用户“这段内容出自哪份文档的哪一页”,这对企业场景非常重要。
  • 数据安全:文本切片向量化后存在本地数据库里,LLM推理也发生在本地,全程不经过第三方接口。
  • 成本可控:只需要一台配置还行的消费级机器,不用租GPU服务器做训练。

所以这个项目的核心路径是:文档加载 → 文本拆分 → 向量化嵌入 → 存入向量库 → 查询时做相似度检索 → 把检索结果拼进Prompt → 本地模型生成答案。

1.3 适合谁来参考

如果你属于以下几类人,这篇内容应该能帮你少走很多弯路:

  • 企业内部想搭一个“文档问答机器人”的技术人员,要求数据必须留在内网。
  • 个人知识管理爱好者,想把本地几百份笔记、电子书、论文变成可对话的资料库。
  • 已经在用LangChain但一直被各类依赖版本问题折磨的开发者,想换个更轻的本地方案。
  • 对Ollama感兴趣,但不知道如何跟LangChain串起来的初学者。

我下面写的内容会从环境搭建、原理拆解、实际编码、问题排查四个维度展开,最后附带整理好的教程文档合集说明。里面所有代码和步骤都是我实际跑通过的,你可以直接照着做。

2. 技术选型与整体架构设计

2.1 核心组件选型对比

在动手之前,我把方案里每个组件都做了横向对比,选型逻辑如下:

组件候选方案我最终的选择选型理由
大模型运行时Ollama / llama.cpp / LocalAIOllama安装简单、模型管理方便、自带HTTP API,对LangChain支持最友好
向量数据库Chroma / FAISS / Qdrant / MilvusChroma(轻量场景)/ FAISS(批量实验)Chroma无需独立服务,直接嵌入进程,适合单机私有部署;数据量大再换Qdrant
嵌入模型nomic-embed-text / all-MiniLM-L6-v2 / bge-m3nomic-embed-textOllama直接支持,中文效果尚可,兼顾速度和体积
文档加载器LangChain内置 / Unstructured / PyPDFLoaderLangChain内置为主生态齐全,PDF/Word/HTML都有对应Loader
编排框架LangChain / LlamaIndex / 原生代码LangChain链式调用、检索器抽象、Prompt模板管理成熟,社区资料最多

说下我为什么没选LlamaIndex。LlamaIndex在纯文档索引场景确实很强,但LangChain的生态更通用,以后想接Agent、接外部工具、换模型都比较顺手。而且LangChain 0.1之后API稳定了很多,网上踩坑资料也齐,对新手更友好。

2.2 整体架构图与数据流

整个系统跑起来之后的数据流是这样一个闭环:

  1. 用户上传或指定本地文件夹,程序遍历所有文档,根据扩展名选择对应的加载器。
  2. 加载出来的原始文本按固定块大小(我默认用500字符,重叠50字符)切分成多个文本块。
  3. 每个文本块调用嵌入模型生成向量,连同原始文本、元数据(文件名、页码等)一起写入向量数据库。
  4. 用户提问时,先对问题做同样的嵌入,然后到向量库执行相似度检索,召回Top-K个最相关的文本块。
  5. 把用户问题 + 召回文档块 + 提示词模板,一起发给Ollama中的本地大模型。
  6. 模型基于上下文生成回答,同时我们在界面上展示命中了哪些文档来源。

这个流程里有两个容易出问题的点:文本拆分参数和向量检索的召回数量。拆分太小,上下文碎片化;拆分太大,检索精度下降。召回太少,可能漏掉关键信息;召回太多,塞给模型的无关内容会干扰判断。我后面的实操部分会给出经过测试的参数区间。

2.3 目录结构与工程组织

我给项目设计了一个清晰的目录结构,避免后期文件越堆越乱:

llm-rag-local/ ├── data/ # 原始文档存放目录 │ ├── source/ # 待入库的PDF、Word、TXT等 │ └── processed/ # 中间文本缓存 ├── vector_store/ # Chroma持久化目录 ├── app/ │ ├── ingest.py # 文档入库脚本 │ ├── query.py # 查询问答脚本 │ ├── chain.py # LangChain链的组装 │ └── prompts.py # Prompt模板 ├── requirements.txt └── README.md

我自己在实践里吃过亏:一开始把所有逻辑写进一个脚本,结果文档一多,每次调试都要重新加载全部文件。现在拆成ingest和query两个流程,入库是一次性的,查询是频繁的,两者分开后效率高很多。

3. 环境准备与依赖安装

3.1 Ollama安装与模型拉取

Ollama的安装非常简单,官方支持Windows、macOS和Linux。以我用的Ubuntu服务器为例:

curl -fsSL https://ollama.com/install.sh | sh

装完之后先确认服务状态,然后拉取两个模型:一个做嵌入,一个做对话。

ollama serve ollama pull nomic-embed-text ollama pull qwen2.5:7b

我选择qwen2.5:7b作为对话模型,主要看中它对中文的支持。如果你机器配置一般(16G内存左右),也可以换qwen2.5:3b或者llama3.2:3b。嵌入模型nomic-embed-text体积只有几百MB,效果在本地场景够用了。

这里必须提醒:Ollama默认监听127.0.0.1:11434,如果想让局域网内其他机器访问,需要设置环境变量OLLAMA_HOST=0.0.0.0。但私有化部署场景下,除非有明确需求,否则不要轻易开公网端口。

3.2 Python虚拟环境与依赖安装

我建议用Python 3.10以上版本,创建一个干净的虚拟环境:

python3 -m venv venv source venv/bin/activate pip install --upgrade pip

然后安装核心依赖。这里我给出一个经过验证的requirements.txt版本,避免装到不兼容的版本:

langchain==0.2.16 langchain-community==0.2.12 langchain-ollama==0.1.3 chromadb==0.5.5 faiss-cpu==1.8.0 pypdf==4.3.1 python-docx==1.1.2

注意:LangChain 0.2之后的版本里,很多第三方组件被拆到langchain-community里了,比如文档加载器。只装langchain主包会报ModuleNotFoundError。这是新手上路最容易踩的坑。

3.3 验证Ollama与LangChain连通性

装完依赖之后,先跑一个最小测试,确认两条链路都通:

from langchain_ollama import OllamaLLM, OllamaEmbeddings # 测试对话模型 llm = OllamaLLM(model="qwen2.5:7b", base_url="http://localhost:11434") resp = llm.invoke("一句话介绍什么是RAG") print("LLM响应:", resp) # 测试嵌入模型 embeddings = OllamaEmbeddings(model="nomic-embed-text", base_url="http://localhost:11434") vec = embeddings.embed_query("私有知识库") print("向量维度:", len(vec))

如果这段代码能正常打印结果,说明LangChain和Ollama的桥梁已经打通了。我在实际测试中,嵌入向量维度是768,这和你选的嵌入模型有关,不同模型维度不同,不用纠结数值。

4. 文档加载与文本拆分的实操细节

4.1 多源文档加载器的选择

这个项目要处理“多源文档”,所以加载器这块值得认真梳理。LangChain里常用的加载器我整理如下:

文档类型加载器说明
PDFPyPDFLoader / PyMuPDFLoaderPyMuPDF速度快,但对扫描版PDF无能为力
WordDocx2txtLoader支持.docx,不支持老的.doc
纯文本TextLoader需要指定编码,否则中文乱码
MarkdownUnstructuredMarkdownLoader可以保留标题结构
HTMLBSHTMLLoader基于BeautifulSoup解析
CSVCSVLoader每行作为一个文档

我项目的ingest.py里写了一个统一的文件遍历逻辑,根据后缀名自动分派加载器:

import os from pathlib import Path def load_document(file_path: str): ext = Path(file_path).suffix.lower() if ext == ".pdf": from langchain_community.document_loaders import PyPDFLoader loader = PyPDFLoader(file_path) elif ext == ".docx": from langchain_community.document_loaders import Docx2txtLoader loader = Docx2txtLoader(file_path) elif ext == ".txt": from langchain_community.document_loaders import TextLoader loader = TextLoader(file_path, encoding="utf-8") elif ext == ".md": from langchain_community.document_loaders import UnstructuredMarkdownLoader loader = UnstructuredMarkdownLoader(file_path) elif ext == ".html": from langchain_community.document_loaders import BSHTMLLoader loader = BSHTMLLoader(file_path) else: raise ValueError(f"不支持的文件类型: {ext}") return loader.load()

有两点经验值得分享。第一,扫描版PDF必须先做OCR,否则加载出来全是空文本,后续检索根本命中不了。第二,PyPDFLoader对部分加密PDF会报错,遇到这种情况先用第三方工具解除密码限制。

4.2 文本拆分策略:块大小与重叠的选择

文本拆分是RAG效果的分水岭。我在项目里做过对比实验,块大小从200字符到1000字符,用同一批文档、同样的3个问题做测试,结论如下:

  • 块大小200-300:检索精度高,但上下文经常不够完整,模型容易漏掉前后文信息。
  • 块大小500左右:综合效果好,既能保留足够的语义上下文,又能保证检索单元相对聚焦。
  • 块大小超过800:检索结果经常包含大量不相关内容,答案准确率明显下降。

所以我最终选了RecursiveCharacterTextSplitter,块大小500字符,重叠50字符。

from langchain_text_splitters import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ".", "!", "?", ";", ";"], )

这里分享一个很多教程不会说的细节:中文文档的分隔符一定要包含中文标点。默认的separators只包含英文换行和英文句号,对中文长文的效果很差。我在separators里加了“。!?”等标点,切出来的块更加完整自然。

另外,如果你处理的是技术文档或Markdown,可以考虑用MarkdownHeaderTextSplitter,它会按标题层级切分,每个块带上所属章节的标题作为上下文元数据,检索命中率会高不少。

4.3 元数据保留:让答案可以溯源

为了让最终的问答能显示“来源文档”,我们在入库时要保留元数据。以PDF为例,加载器默认会给每个Document对象填充source和page字段。但如果你使用自定义加载器,最好手动补充:

docs = load_document(file_path) for doc in docs: doc.metadata["source_file"] = os.path.basename(file_path) doc.metadata["source_path"] = file_path

这些元数据在后续检索展示时非常有用,它能让用户看到答案来自哪里,增加可信度。我在最终的查询输出里,会把命中的文档名、页码、文本块内容一并打印出来。

5. 向量数据库与检索链路的实现

5.1 为什么选Chroma

向量数据库的选型,很多人一上来就想上Milvus或者Qdrant这种独立服务。但对于个人级、小团队级私有知识库,这属于杀鸡用牛刀。Chroma的优势在于:

  • 零配置,pip安装完直接嵌入Python进程,不需要单独维护服务端。
  • 持久化目录是一个普通文件夹,备份迁移非常方便。
  • API设计简洁,和LangChain集成度高。

如果你的文档量到了百万级,再考虑换Qdrant或者Milvus不迟。我见过很多朋友第一步就在K8s上折腾向量库,结果核心RAG逻辑还没跑通就劝退了。

5.2 入库流程完整实现

下面是我ingest.py里最核心的一段代码,逻辑是遍历data/source目录下所有文档,加载、拆分、向量化、写入Chroma:

from langchain_community.vectorstores import Chroma from langchain_ollama import OllamaEmbeddings PERSIST_DIR = "./vector_store/chroma_db" def ingest_documents(): embeddings = OllamaEmbeddings(model="nomic-embed-text") all_docs = [] source_dir = Path("./data/source") for file_path in source_dir.rglob("*"): if file_path.is_file() and file_path.suffix.lower() in {".pdf", ".docx", ".txt", ".md", ".html"}: print(f"正在处理: {file_path}") docs = load_document(str(file_path)) for doc in docs: doc.metadata["source_file"] = file_path.name all_docs.extend(docs) if not all_docs: print("未找到任何有效文档") return splits = text_splitter.split_documents(all_docs) print(f"文档切分为 {len(splits)} 个文本块") vector_store = Chroma.from_documents( documents=splits, embedding=embeddings, persist_directory=PERSIST_DIR, ) vector_store.persist() print(f"向量库构建完成,持久化至 {PERSIST_DIR}")

这里有一个新手容易忽略的问题:Chroma的persist()方法在最新版本里会在每次写入时自动落盘,但显式调用一下更稳妥。另外,重复执行ingest脚本会导致向量库里的文档重复,所以我在脚本里加了一个reset参数,需要重建库时先删除persist目录再重新写入。

5.3 相似度检索的参数选择

入库完成后,查询侧最关键的是检索器参数。我用Chroma的as_retriever方法创建检索器,search_kwargs里主要调两个参数:

retriever = vector_store.as_retriever( search_type="similarity", search_kwargs={"k": 4} )

k是召回数量。我测试过k=2到k=8,结论如下:

  • k=2:回答经常信息量不足,尤其对于需要多文档交叉印证的问题。
  • k=4:绝大多数场景的最佳选择,上下文和干扰达到平衡。
  • k=6以上:回答开始出现偏离,因为无关文本块混进来了。

如果你用的是similarity_score_threshold搜索类型,还可以设置score_threshold过滤低相似度结果。但要注意,不同嵌入模型的分数分布不同,nomic-embed-text的分数普遍较高,阈值可以设在0.3左右;换其他模型可能要重新标定。

5.4 组装完整的问答链路

最后一步是LangChain链的组装。我用了一个很经典的RetrievalQA结构:

from langchain.chains import RetrievalQA from langchain_ollama import OllamaLLM from langchain.prompts import PromptTemplate prompt_template = """你是一个企业内部知识库助手。 请根据以下资料片段回答用户的问题。 如果资料中没有相关信息,请直接回答“资料库中暂无相关信息”,不要编造。 资料片段: {context} 用户问题:{question} 请给出简洁、准确的回答,并在回答末尾标注信息来源文件名。""" QA_CHAIN_PROMPT = PromptTemplate( input_variables=["context", "question"], template=prompt_template, ) def build_qa_chain(): llm = OllamaLLM(model="qwen2.5:7b", temperature=0.1) vector_store = Chroma( persist_directory=PERSIST_DIR, embedding_function=OllamaEmbeddings(model="nomic-embed-text"), ) qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", retriever=vector_store.as_retriever(search_kwargs={"k": 4}), return_source_documents=True, chain_type_kwargs={"prompt": QA_CHAIN_PROMPT}, ) return qa_chain

这里chain_type="stuff"表示把所有检索到的文本块直接塞进Prompt。对于召回4个、每块500字符的情况,总共约2000字符,qwen2.5:7b的上下文窗口完全扛得住。

我实测发现temperature=0.1比较合适。温度太高模型会“自由发挥”,回答里出现资料中没有的内容;温度太低则显得机械,几乎照抄原文。对于知识问答场景,0.1到0.3这个区间最优。

6. 查询演示与效果评估

6.1 单条查询的完整输出

我用一份内部产品手册和一份行业报告建了库,然后问了一个需要跨文档才能回答的问题:“根据所提供资料,产品A在2024年第四季度发货量变化的关键原因有哪些?”

实际输出简化后大致如下:

正在检索相关知识... 问题: 根据所提供资料,产品A在2024年第四季度发货量变化的关键原因有哪些? 回答: 根据资料,产品A在2024年第四季度发货量环比下降12%,关键原因包括: 1. 上游芯片供应交期从8周延长至14周,导致整机交付延迟; 2. 海外市场渠道库存偏高,客户主动放缓提货节奏; 3. 第四季度新品切换期,部分产线产能用于新品试产。 来源信息: - 产品A年度运营分析报告.pdf 第3页 - 2024年行业渠道洞察.docx 第7页

这个输出说明整个链路是通的:检索模块确实从两个不同文档里都找到了相关内容,模型也正确地把信息合并且标注了来源。

6.2 检索效果自评思路

RAG项目不能只靠“看起来回答了”来评判,我建议用一组固定的测试问题做回归评估。我给自己的项目建了一个包含15个问题的测试集,分三类:

  • 事实型问题:答案应该能在某一份文档中直接找到。
  • 综合型问题:需要从多个文档或多个文本块中归纳。
  • 拒答型问题:文档中没有答案,模型不应该编造。

每次调整任何参数(比如嵌入模型、切块大小、召回数量)后,跑一遍测试集,对比回答中“完全正确/部分正确/错误/编造答案”的占比。我实测从默认切块参数调整到中文分隔符之后,综合型问题的正确率大约提升了20%。

7. 常见问题与排查技巧实录

7.1 依赖版本不兼容导致的诡异报错

我在实际搭建过程中遇到最多的问题就是LangChain各组件版本不匹配。社区里很多人报错ModuleNotFoundError: No module named 'langchain_community',原因就是只安装了langchain主包,而加载器和向量库都搬到了langchain-community里。

解决方案很简单:安装langchain-community,并且让langchain、langchain-community、langchain-ollama三者的版本尽量保持同一个大版本。不要一个装0.1一个装0.3,API差异能让你怀疑人生。

7.2 Ollama模型加载内存不足

如果你在回答问题时发现第一次请求特别慢,甚至直接OOM(内存溢出),先检查机器的可用内存。qwen2.5:7b量化版大约需要6-8GB内存,再加上嵌入模型和Chroma,建议16GB起步。

如果内存紧张,可以换更小的模型,比如qwen2.5:3b或者phi3:mini。我实测qwen2.5:3b在简单事实型问题上的表现完全可用,只是复杂推理稍弱。另一个技巧是设置OLLAMA_KEEP_ALIVE=600,让模型保持在内存中不释放,避免每次问答重新加载。

7.3 中文乱码与编码问题

TextLoader加载TXT文件时,默认按UTF-8解码,如果源文件是GBK编码的,就会报UnicodeDecodeError。我在加载器里显式指定了编码:

loader = TextLoader(file_path, encoding="utf-8", autodetect_encoding=True)

autodetect_encoding=True会自动探测编码,实测对GBK、GB2312文件都能正确处理。

7.4 入库后查询不到任何结果

这种情况先排查三件事:

  1. 向量库是否确实写入成功——查一下persist目录下有没有数据文件,或者重新执行一次ingest并观察日志。
  2. 嵌入模型是否一致——入库和查询必须用同一个嵌入模型,否则向量空间完全不同,检索结果等于随机。
  3. 文档是否加载出空文本——很多PDF看起来有字,实际上是扫描图片,加载出来是空字符串。用脚本打印len(docs[0].page_content)即可判断。

7.5 回答质量不佳时的调优顺序

如果回答总是不准确,我建议按这个顺序调优,不要病急乱投医:

  1. 先检查召回内容:直接打印检索器返回的文本块,看看相关段落是否被正确召回。
  2. 如果召回不准确:调小chunk_size,或者换一个嵌入模型试试。
  3. 如果召回准确但回答不准:优化Prompt模板,明确告诉模型“只能基于资料回答”。
  4. 如果回答喜欢编造:降低temperature,同时增加一个兜底指令。
  5. 如果资料特别长:增加召回数量k,或使用map_reduce链类型做多文档综合。

8. 项目扩展方向与个人心得

8.1 这个项目还能怎么继续演进

现在这个版本是单机单用户的命令行形态,如果你要把它变得真正可用,有几个扩展方向:

  • 加一个Web界面:用Gradio或Streamlit包一层,几分钟就能做一个可视化的问答页面,支持上传文档、流式输出。
  • 支持并发查询:把推理部分拆成独立服务,用消息队列接收查询请求,适合多用户同时使用。
  • 引入重排序(Rerank):先用向量粗召回Top20,再通过一个交叉编码器精排取Top4,能明显提升复杂问题的回答质量。
  • 增量更新:做一个文件夹监听,新增文件自动触发入库流程。

8.2 我在实际使用中的几点体会

第一个体会是:RAG项目百分之六十的时间都花在数据清洗和切块调参上,而不是模型本身。很多人一上来就追求最新最强的模型,结果发现文档加载环节就问题百出。老老实实把PDF解析好、把文本切干净,效果提升比换大模型更明显。

第二个体会是:Ollama在本地模型管理上的体验真的很好,一条命令拉模型、一条命令跑服务,对开发调试非常友好。但也要清楚它的边界,毕竟消费级显卡跑7B模型,推理速度有限,不适合承载高并发的线上服务。

第三个体会是关于“私有”这两个字。本地部署不等于绝对安全,向量数据库的文本切片里可能包含敏感信息,有条件的团队还是应该做好磁盘加密和访问控制。至少别把包含客户资料的文档随意放在共享目录里。

8.3 附带的教程文档合集使用建议

这次整理的同时,我做了一份配套的教程文档合集,里面包含三部分:一是Ollama的简明使用手册,覆盖模型管理、API调用和常见配置项;二是LangChain对接Ollama的代码示例集,按文档加载、向量化、检索、问答拆成独立脚本;三是RAG调优笔记,记录了我测试不同切块参数、不同召回数量时的对比数据和结论。

我建议你拿到合集后不要从头到尾顺序读,而是先跑通本篇文章里的最小示例,再遇到具体问题时去合集里翻对应章节。文档里我有意保留了出错时的报错信息和排查思路,比只看成功路径更有参考价值。

最后再分享一个我踩过几次坑之后养成的小习惯:每次修改代码前,先把向量库备份一下,或者记录当前版本对应的参数组合。因为RAG项目的调优是迭代式的,改乱了想回退,没有备份就只能重新入库,有些文档源文件一旦丢失,重建的代价非常高。有了这个习惯,你有底气大胆试错,也更容易找到最适合自己的那组配置。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 10:07:04

五大越老越吃香的技术方向:网络安全、数据库、SRE等

最近一两年,我跟不少做开发、做算法的朋友聊天,大家普遍有一个感觉:手上的技术框架更新太快,今天还在调模型参数,明天又要适配新框架,技术栈看着越来越新,心里却越来越虚。反倒是网络安全、数据…

作者头像 李华
网站建设 2026/10/16 10:07:00

Flutter应用迁移鸿蒙:data_size适配实战

把 Flutter 应用迁到鸿蒙端之后,第一个让人头疼的往往不是复杂的业务逻辑,而是那些看起来不起眼的小工具库。数据大小换算就是典型的例子:一段内存占用、一个下载页的进度、文件管理里的大小展示,看起来一句话的事,真要…

作者头像 李华
网站建设 2026/10/16 10:05:29

移动机器人部署全流程:从硬件装配到导航调试的工程实践

1. 从一堆散件到能跑的地面无人平台:移动机器人部署到底在部署什么很多人第一次听到“移动机器人部署”这个词,脑子里浮现的画面可能是把一台已经组装好的机器人从箱子里搬出来、按下电源键、然后它就开始满地跑。我当初也是这么想的,直到真正…

作者头像 李华
网站建设 2026/10/16 10:04:19

STM32F103ZET6裸机示波器程序:ADC+DMA+定时器触发与TFT屏幕波形显示实战

简介:这是一份面向嵌入式初学者与电子爱好者的STM32F103ZET6简易示波器程序源码包,基于Cortex-M3内核芯片实现正弦波、方波等信号的采集与波形显示,适合用于学习ADC采样、LCD驱动与定时器配置等典型外设开发。压缩包共272个文件,约…

作者头像 李华
网站建设 2026/10/16 10:04:18

二手服务器搭建私有AI平台:从选模型到部署推理引擎全指南

聊到私有 AI 平台,我猜很多人和我一样,第一反应是"搞台好机器装个界面就算搭完了"。但真到自己动手,尤其用的还是一台二手服务器,事情远没有这么简单。 这是这个系列的第三篇。前面两篇我分别讲了怎么淘硬件、装机、做…

作者头像 李华
网站建设 2026/10/16 10:04:14

Flutter手势与点击事件全解析:从基础组件到手势竞技场与实战避坑

在 Flutter 里做点击事件,看起来是个入门得不能再入门的话题,但实际写起来,很多人都会在“为什么 onTap 没反应”“为什么水波纹不见了”“为什么拖动和列表滚动打架”这类问题上卡壳。尤其是当你开始做手势组合、自定义交互的时候&#xff0…

作者头像 李华