LangChain学习笔记:从零构建RAG知识库问答系统的实战复盘

这篇LangChain学习笔记,我拖了比较久才写。

不是不想写,是这东西变化太快。今天写好的代码,过两周再看可能就已经被标记为弃用。网上关于 LangChain 的教程,绝大多数是把官网文档翻了个面,标题写着“入门到实战”,正文却停在 Hello World。我觉得真正值得沉淀下来的,是自己从零调通一个本地知识库问答项目的完整过程——包括为什么要这样组链、参数为什么这样设、报错到底是什么原因,以及哪些是文档永远不告诉你的细节。

这篇笔记适合两类人:一类是刚接触 LangChain,想知道它到底能干什么、跟直接调大模型接口有什么区别;另一类是已经在用,但卡在检索不准、记忆混乱、版本报错这些实际问题上的人。我会尽量少说空话,直接把设计思路、核心组件和可落地的代码方案交代清楚。

1. 先搞懂 LangChain 的核心设计:它解决的是组装问题,不是模型问题

1.1 它到底做了什么

很多人一开始以为 LangChain 是某种大模型,或者是一套模型服务平台。其实不是。它本质上是编排层,负责把模型、提示词、记忆、检索、工具这些零部件按不同方式接起来。用生活化一点的类比:大模型好比一台发动机,LangChain 是给这台发动机接线、装变速箱、接仪表盘的那套系统。发动机很重要,但一辆车能不能好开,取决于整车怎么装配。

我学习时的第一个误区是急于写代码调 Agent,结果连“链”和“代理”的关系都没理清。LangChain 的核心抽象是 Chain,即一条确定性的处理流水线:输入进来,依次经过提示词格式化、模型调用、输出解析,最后返回结果。Agent 则是在这条流水线上加了循环和工具选择,本质上更复杂。后来官方用 LangGraph 实现图式流程编排,底层思路一致:把任务拆成节点,把节点连成图,逐段执行。

所以我的建议是,如果决定学 LangChain,先种下这个概念:学的是“组装方式”,不是“模型本身”。模型能力决定输出天花板,框架决定工程质量和可控性。很多人搭出来的 RAG 效果差,不一定是模型不行,反而是组装链路里某些环节太粗糙。

1.2 版本演进:先看版本,再看教程

还有一个必须提醒的点:LangChain 的版本分裂非常严重。0.1 时代很多代码写的是from langchain.llms import OpenAI,到了 0.2、0.3 时代这行已经不够用了,包被拆成langchain-core、langchain-openai、langchain-community、langchain-text-splitters等。刚开始我用旧教程练手,第一行 import 就报错,后来才意识到问题不在我,而是版本差异。

我现在的做法是:学习前先确认自己装的版本,代码里优先从langchain_core导入基础抽象,模型类从langchain_openai或langchain_community导入。遇到报错先查版本迁移文档里那个被改名的类,一般都能解决。

比如同样是加载文本,旧版写成from langchain.document_loaders import TextLoader,新版本更规范的路径是from langchain_community.document_loaders import TextLoader。如果看到网上教程的 import 路径扎堆混乱,不要怀疑自己,先看看教程发布日期和版本号。

1.3 什么场景真正有必要用它

也不是所有项目都必须上 LangChain。如果只是调一次模型、写一个翻译小脚本,直接用请求库打接口反而干净。LangChain 的价值在场景变复杂时体现:多个模型切换、多轮对话需要记忆、要给模型接公司内部知识库、要让模型调用工具或执行多步任务。把它当胶水用,它的优势很大;把它当万能框架用,反而给自己加戏。

我自己判断是否引入 LangChain 的标准很简单:如果任务里出现了“提示词需要动态组装”“对话超过三句”“回答需要参考外部资料”三个特征中的任意两个,就值得引入。如果只是单轮问答,裸调接口更省事。

2. 核心组件逐个拆解:模型、提示词、记忆、检索、输出解析

2.1 模型封装:同一个接口切换不同模型

LangChain 对模型的封装是最基础的一层。它会帮你处理消息格式、流式输出、缓存、失败重试等细节。

在 LangChain 里,模型接口被抽象成两种:纯文本补全模型和对话模型(Chat Models)。现在新项目尽量用对话模型,因为返回消息带角色,方便处理多轮对话。

from langchain_openai import ChatOpenAI llm = ChatOpenAI( model="demo-chat", base_url="https://your-endpoint.example.com/v1", api_key="your-key", temperature=0.3, )

ChatOpenAI这个名字容易让人误以为只能连特定厂商的接口。实际上只要服务走的是 OpenAI 兼容协议,基本都能通过base_url接入。这样做的直接收益是:业务代码不感知模型厂商,想从 A 模型切到 B 模型,只改配置,不动链路。在真实项目里,这个灵活性非常值钱,因为模型迭代太快,今天觉得好用的模型,下个月可能被另一个模型在效果和价格上双杀。

2.2 提示词模板:别再用 f-string 拼提示词

很多人写提示词喜欢直接f"你是{role},请回答{question}"。在简单脚本里没问题,一旦工程化,这类字符串散落在代码各处,改起来非常痛苦。

LangChain 提供了PromptTemplate和ChatPromptTemplate。ChatPromptTemplate 更适合对话模型,因为它能区分 system、human、ai 消息。

from langchain_core.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_messages([ ("system", "你是一名{role},回答必须简洁,不要编造事实。"), ("human", "{question}"), ]) messages = prompt.invoke({"role": "客服助手", "question": "怎么申请退款?"}) print(messages.to_messages())

提示词模板不只是把变量套进字符串,它还把消息角色结构固化下来。比如 system 消息负责定人设,human 消息负责承载用户输入。更重要的是,模板可以独立维护和版本管理,换提示词不需要改代码逻辑。

实际项目中,我习惯把 prompt 单独放进一个文件或配置项,关键内容做到可视化修改。尤其当你发现自己频繁调整提示词的时候,模板化的收益会迅速超过手工拼接。

2.3 记忆模块:别让它无限制累积

大模型本身没有记忆,每次调用都是无状态的。多轮对话时如果不把历史信息传给模型,模型不会记得上一句说了什么。

LangChain 提供了几种记忆方案,名字容易混淆:

记忆类型工作方式适合场景成本
ConversationBufferMemory全部历史拼接短对话、调试token 消耗高
ConversationBufferWindowMemory只留最近 N 轮常规客服场景可控
ConversationSummaryMemory模型压缩历史,存摘要长对话有压缩开销且会丢细节

一开始我无脑使用 BufferMemory,结果对话到第 20 轮时 prompt 长度直接爆炸,报错提示超出上下文上限。后来切到窗口记忆,只保留最近 6 轮,问题立刻解决。

一个更可控的做法是手动维护消息列表。在 LCEL 管道里,记忆不一定要绑定在 Chain 内部,直接把历史拼进 prompt 反而透明:

history = [] # 每个元素是 {"human": "用户问题", "ai": "模型回答"} def build_messages(question): messages = [] for item in history[-6:]: messages.append(("human", item["human"])) messages.append(("ai", item["ai"])) messages.append(("human", question)) return messages message_dicts = build_messages("那请问它支持企业微信吗?") prompt = ChatPromptTemplate.from_messages(message_dicts)

招数朴素,但好处很明确:每条消息都清晰可见,调试时一眼看清模型到底收到了什么。生产环境里,可观测性永远是第一位。

2.4 文档加载与切割:RAG 效果一半由这里决定

RAG 项目里最容易被低估的环节是文档处理。LangChain 对文档的抽象是Document,它包含两个字段:page_content存放文本正文,metadata存来源、页码、标题等附加信息。

文档加载完成后,通常不能直接把整个文档丢给向量库。因为一篇文章可能几千字,直接向量化会稀释语义,检索精度很差。正确做法是先切分成小块,每个块作为一个独立检索单元。

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

chunk_size=500意味着每个块大约 500 个字符,chunk_overlap=50让相邻块有 50 字符重叠。overlap 的作用是避免关键句恰好被拦腰截断,导致上下文丢失。切太细,单块语义不完整;切太粗,块内噪声多,检索命中率下降。500 到 800 是我在文档问答项目里常用的区间,具体要看文档类型。

这里特别想强调中文场景。默认的 separator 列表对英文友好,但中文没有空格,默认方案会把中文句子切得比较碎。我在 4.2 节会展开讲这个坑。

2.5 检索器、向量库与输出解析:把结果变得可预期

Retriever的职责是:给一个查询,返回若干相关 Document。最常见的是把向量库包装成检索器。

retriever = db.as_retriever( search_type="similarity", search_kwargs={"k": 5}, )

k表示返回多少条结果。为什么是 5 而不是 20?一方面受模型上下文长度限制,另一方面结果越多,干扰信息也越多。但在后续章节我会推荐一个“先召回更多、再二次筛选”的进阶做法。

输出解析器也是容易被忽略的组件。模型返回的是纯文本,但业务往往需要结构化的数据。StrOutputParser直接取文本内容;JsonOutputParser要求模型输出 JSON;PydanticOutputParser可以指定数据结构和字段说明,解析失败时还会报错提示。

from langchain_core.output_parsers import PydanticOutputParser from pydantic import BaseModel, Field class Answer(BaseModel): answer: str = Field(description="最终回答内容") confidence: float = Field(description="置信度,0到1之间") sources: list[str] = Field(description="参考来源编号列表") parser = PydanticOutputParser(pydantic_object=Answer)

输出解析器让模型输出从“靠运气”变成“可校验”。在需要对接后台系统的场景里,这一步几乎是刚需。

3. 实操记录:从零搭一个本地知识库问答系统(RAG)

现在进入正题。我用一个模拟项目X来说明完整流程:项目里有一批产品说明文档,目标是让用户用自然语言提问,模型从文档里找答案,而不是凭训练数据瞎编。

3.1 数据准备:从原始文档到干净文本

这一步最不性感,但收益最高。我遇到的第一个问题不是代码,而是文档本身。有的文本从 PDF 抽取后带乱码,有的表格被转成了断行文本,有的文档开头带一堆没用的页眉页脚。

我先把所有文档统一转成纯文本,编码统一为 UTF-8,然后做一轮清洗:去掉多余的空白行、去掉页眉页脚、把表格描述成“字段名是 xxx,值是 xxx”的句子。清洗后的质量会直接影响切分和检索效果。

加载文本用TextLoader:

from langchain_community.document_loaders import TextLoader loader = TextLoader("./kb/产品说明.txt", encoding="utf-8") docs = loader.load()

如果一个目录下有很多文件,可以用DirectoryLoader批量加载。注意指定编码,中文环境下默认编码经常踩坑,与其猜测,不如显式写清楚。

3.2 切分参数:chunk_size 与 overlap 的权衡

切分是把长文档切成语义相对独立的小块。我在项目里用的是:

from langchain_text_splitters import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""], ) chunks = splitter.split_documents(docs) print(f"共切分为 {len(chunks)} 个片段")

为什么选 500?因为太短的 chunk 往往只有一两句话,语义不完整,模型回答时缺少上下文;太长的 chunk 又会让向量检索命中时夹带大量无关内容。选 50 的 overlap,是给边界信息一个缓冲,防止一句完整的话被分到两个块里导致两边都不完整。

实际操作时,我会随机抽查几个 chunk,看切分边界是否落在完整语义上。如果发现大量分割点在句子中间,就调整分隔符优先级或加大 overlap。

3.3 向量化与向量库选型:为什么小项目选 FAISS

把文本变成向量是 RAG 的核心环节。Embedding 模型的选择很重要,可以用云端接口,也可以用本地小模型。本地小模型的好处是数据不出内网、调用零延迟,适合内部知识库。

我用的本地模型是一个开源中文 Embedding 模型,体积小,效果够用。加载方式:

from langchain_community.embeddings import HuggingFaceEmbeddings embeddings = HuggingFaceEmbeddings( model_name="bge-small-zh-v1.5", encode_kwargs={"normalize_embeddings": True}, )

向量库方面,我一开始直接在几个方案里纠结了半天。最后选择很简单:

  • 单机、小规模、快速验证:选 FAISS,纯本地文件,零运维
  • 需要可视化翻看数据、多人协作:选 Chroma
  • 大规模分布式、高并发:再考虑云端向量库

FAISS 是一个向量检索库,不是数据库,所以不能当存储系统长期依赖。项目原型阶段用它足够,正式上线前再迁移到更适合的存储。

构建索引:

from langchain_community.vectorstores import FAISS db = FAISS.from_documents(chunks, embeddings) db.save_local("./faiss_index")

下次启动代码时加载:

db = FAISS.load_local( "./faiss_index", embeddings, allow_dangerous_deserialization=True, )

加载时这个参数是新版本要求显式确认的,主要防止反序列化风险。自己本机的索引可以放心打开,但如果是外部传入的索引文件,就要谨慎对待。

3.4 组装 RAG 链路:LCEL 管道写法

LangChain 0.2 以后主推 LCEL,用|符号把组件像管道一样连起来。下面是一个完整的 RAG 问答链路。

from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个严谨的文档问答助手。请仅根据给定上下文回答问题。" "如果上下文中没有相关信息,请直接说不知道,不要编造。\n\n" "上下文:\n{context}"), ("human", "{question}"), ]) llm = ChatOpenAI( model="demo-chat", base_url="https://your-endpoint.example.com/v1", api_key="your-key", temperature=0, ) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) result = rag_chain.invoke("我们的产品支持哪些支付方式?") print(result)

这段代码里,retriever先根据问题检索出若干文档,format_docs把它们拼成一段上下文;RunnablePassthrough()把用户原样传入作为 question;随后交给 prompt 模板格式化,再送入模型,最后取出纯文本结果。

3.5 检索调参:召回 20,二次筛选取 5

基础链路跑通后,我发现一个典型问题:有时候正确答案在第 6 条、第 8 条的位置,只取前 5 条就把正确答案漏掉了。

解决办法是改成两段式检索。第一段召回 20 条,保留足够多的候选;第二段用相似度分数过滤,或者用重排序模型压缩到 5 条。

docs_with_scores = db.similarity_search_with_score("怎么申请退款", k=20) filtered = [] for doc, score in docs_with_scores: # FAISS 返回的距离,分数越小代表越相似 # 具体阈值需要先打印看分布,不能直接套网上的值 if score < 50: filtered.append(doc) context = format_docs(filtered[:5])

这里特别想提醒:不同向量库、不同 Embedding 模型计算出来的分数范围完全不一样。有的库返回的是距离,有的返回余弦相似度,连“越大越好还是越小越好”都不一样。我第一次调试时拿网上的阈值直接套,结果把所有结果都过滤掉了,打印出来一看,分数分布和我以为的差了十万八千里。

正确的习惯是:先打印 20 条分数,观察相关结果和不相关结果的分布,再选择分割点。

3.6 加上历史记忆:让追问不再断片

用户提问往往是一连串的。用户问“你们有什么套餐?”,接着会问“第一个多少钱?”如果没有历史,第二个问题就是一串无意义的字符,检索效果很差。

做法是在进入 RAG 链路之前,先把最近几轮对话拼进 prompt:

history = [] def ask(question): messages = [] for item in history[-6:]: messages.append(("human", item["human"])) messages.append(("ai", item["ai"])) messages.append(("human", question)) prompt_cur = ChatPromptTemplate.from_messages(messages) chain = prompt_cur | llm | StrOutputParser() answer = chain.invoke({}) history.append({"human": question, "ai": answer}) return answer

注意这里没有直接在 prompt 里用变量,而是把历史消息作为多轮对话传入,模型能区分哪句是谁说的。检索部分也可以额外加一层:把“第一个”这类代词改写为原始问题里的实体,这是进阶优化,可以先不做。

4. 常见报错与排查技巧实录

4.1 高频报错速查表

我把实际项目里遇到的高频问题整理成了表格,方便直接对照。

报错信息常见原因处理方式
ModuleNotFoundError: No module named 'langchain_openai'包版本拆分,旧写法失效安装对应分包:pip install langchain-openai
ImportError: cannot import name 'LLMChain' from 'langchain'代码基于 0.1 老版本改用 LCEL 管道写法,或升级迁移
OutputParserException: Failed to parse模型返回内容不是合法 JSON换结构化输出,解析失败后重试
This model's maximum context length is ...输入超出上下文限制减小 chunk、缩短历史、用摘要压缩
IndexError: Faiss with no index loaded索引文件不存在或路径不对检查是否执行过save_local,路径是否正确
InvalidPromptError: Missing input variablesprompt 模板变量没有传全打印模板的输入变量列表,逐一核对

版本类报错在 LangChain 里占比最高。如果看到某段旧代码一堆 import 报错,不要逐个硬改,直接去官方升级指南搜索改名后的类,效率最高。

4.2 中文切分乱码与语义割裂

我在项目里第一次使用默认的RecursiveCharacterTextSplitter,切分结果惨不忍睹。默认分隔符列表是["\n\n", "\n", " ", ""],对英文友好,但中文没有空格,最终会退化到按单个字符切割,chunk 边界经常落在一个完整句子的中间,导致检索时语义被拦腰截断。

修正方案是显式加入中文断句标点:

splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""], )

RecursiveCharacterTextSplitter会按顺序尝试分隔符,先按段落分,再按句子分,这样切出来的块语义通常更完整。切完以后我强烈建议打印几个 chunk 人工看一眼,这一步花不了两分钟,但能省掉后面大量检索调试时间。

4.3 空检索、弱检索:把召回当成一个漏斗

如果检索结果为空,或者检索到的内容和问题完全无关,我会按下面顺序排查:

  1. 文档是否真的加载进来了。打印len(docs),很多空检索其实是读取文件失败,内容为空。
  2. 切分后的片段是否太碎或太脏。看几个 sample 确认。
  3. Embedding 是否正常工作。可以用一小段文字先向量化,再计算它和自身的相似度,如果分数异常,说明模型加载有问题。
  4. 查询本身是否太模糊。比如用户只输入“你好”,向量检索自然找不到稳定结果。
  5. 阈值是否设得太高。把分数全部打印出来看分布,再决定分割点。

一个很实用的技巧是查询改写。对模糊问题做一次扩展,再分别检索。例如“怎么退款”可以扩展成“退款流程”“退款申请”“退款规则”“取消订单后怎么退款”四个查询,分别检索后合并结果,再让模型综合回答。实现不复杂,但对弱检索的提升非常明显。

4.4 输出格式不稳定:用结构化输出兜底

模型输出经常不够规矩,让它输出 JSON,它可能在前面加一句解释,导致解析失败。第一次遇到时,我的第一反应是修改提示词,后来发现治标不治本,因为模型状态不可控。

更可靠的方案是解析失败时让模型自己修正。伪代码如下:

from langchain_core.output_parsers import JsonOutputParser from langchain_core.runnables import RunnableLambda parser = JsonOutputParser() def safe_parse(text): try: return parser.parse(text) except Exception as e: # 把错误信息拼回去,让模型重新生成合法 JSON fix_prompt = f"你上次的输出不是合法 JSON。错误信息:{e}\n请重新输出,只输出 JSON,不要加任何解释。原始内容:{text}" fixed = llm.invoke(fix_prompt) return parser.parse(fixed.content) chain = prompt | llm | RunnableLambda(safe_parse)

这类重试机制价值很高,能在代码层面兜住模型的不确定性。代价是多一次模型调用,但换来的是链路整体可靠性,完全值得。

5. 我的学习路线与几个反共识的看法

5.1 先别急着学 Agent

很多初学者一上来就想做自主 Agent,结果陷入循环调用、token 烧完、行为不可控的泥潭。我的建议是先把 Chain 用熟,再考虑 Agent。

Agent 的本质是让模型决定下一步调用什么工具,这依赖模型对工具的推理能力。模型不够强时,所谓 Agent 就是灾难:它会在工具之间来回横跳,迟迟不结束任务,还会调用无关工具浪费预算。而 Chain 是确定性的,输入什么就走什么流程,至少可以预期。

RAG 是比 Chain 再高一级的复杂度,但它是确定可调优的。把 RAG 做好之后,再学 Agent 会轻松很多,因为你已经理解模型输出的随机性、工具调用的开销、提示词对决策的影响,面对 Agent 失控时也更容易定位问题。

5.2 推荐的五阶段学习路径

第一阶段:用接口直接调用大模型,理解 temperature、max_tokens、流式输出这些基础概念。不要跳过,因为框架可能会掩盖这些细节,但工程里一定会遇到。

第二阶段:用ChatPromptTemplate加输出解析器做一个简单小工具,比如关键词提取器或文本分类器。目标是理解模板、模型、解析三段式结构。

第三阶段:做一个完整的 RAG 项目,包括文档清洗、切分、向量化、检索、生成、评测。这是性价比最高的一步,所有核心技能都会用到。

第四阶段:优化检索质量,引入记忆、查询改写、重排序、评分过滤。这些优化对效果的提升往往比换模型更明显。

第五阶段:根据需求决定是否进入 LangGraph 或多 Agent 方向。到这一步再看官方文档,你不再是一头雾水,而是能看懂它解决的是什么问题了。

5.3 几个反共识的看法

第一,LangChain 本身不慢,也不笨重。慢的是我们不加选择地调用模型和向量库,同样的检索写了一圈重循环。链路的每一步最好都记录耗时,再决定优化哪里。

第二,不必为了框架而框架。场景足够简单时,手工拼请求反而更清楚。我不会强行把一个小脚本套进 LangChain,只有出现多组件协作时才用它。

第三,永远把可观测性放在第一位。我在调试 RAG 时最常用的工具不是断点,而是埋头打印每一步的输入输出:prompt 最后长什么样、retriever 召回了哪些文档、分数是多少。很多问题一打印就水落石出。

第四,不要迷信“一键 RAG”。真实项目中,文档清洗、切分、召回评测占掉七成工作量,LangChain 只把剩下三成的胶水活变得干净。如果你发现效果不好,先检查数据和检索,别第一时间怀疑框架。

我现在的习惯是,把每个环节都做成可以单独测试的小模块:文档加载单独测,切分单独看,检索单独打印分数,最后才拼成完整链路。这套方式,是跑了两个完整项目之后沉淀下来的。LangChain 学习笔记可以写得很长,但真正有用的,往往就是这些从报错和调试里逼出来的经验。

相关新闻

LangChain v1.0迁移实战:从LCEL链到LangGraph状态机

LangChain v1.0迁移实战:从LCEL链到LangGraph状态机

最近在把一个基于 LangChain v0.x 的老应用往 v1.0 迁&#xff0c;版本从 v0.3 一路推到 v1.0&#xff0c;中间恰好踩在 LangGraph v1.0-11 这个节点上。这一轮迁移最核心的目标不是“把 import 改一改跑通”&#xff0c;而是系统性梳理升级策略和 Breaking Changes&#xff0c…

2026/10/21 9:03:19 阅读更多 →
artcraft:模板+参数+渲染三层架构实现批量创意图片生成

artcraft:模板+参数+渲染三层架构实现批量创意图片生成

1. 从“artcraft”这个名字说起&#xff1a;它到底想解决什么问题第一次看到“artcraft”这个标题&#xff0c;我脑子里蹦出来的第一反应是&#xff1a;这大概率是一个把“艺术&#xff08;art&#xff09;”和“手工艺/工程化&#xff08;craft&#xff09;”揉在一起的项目。…

2026/10/21 9:03:19 阅读更多 →
Java异常处理最佳实践:从异常体系到线上排查的完整指南

Java异常处理最佳实践:从异常体系到线上排查的完整指南

写 Java 的人&#xff0c;几乎每天都在跟异常处理打交道。不过说实话&#xff0c;大多数项目里异常处理这块的代码质量&#xff0c;往往是整个工程里最容易被忽视的部分。我做过不少代码评审&#xff0c;发现一个规律&#xff1a;业务逻辑写得再花哨&#xff0c;只要异常处理随…

2026/10/21 9:02:18 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年&#xff0c;我大部分时间都在跟浏览器标签页打交道&#xff0c;直到第一次把桌面应用跑起来&#xff0c;才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词&#xff0c;最开始是在某段时间频繁出现在同事的讨论里&#xff0c;一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档&#xff0c;主要面向智能锁安装人员、终端用户及售后维护者&#xff0c;帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件&#xff0c;大小4.37MB&#xff0c;内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统&#xff0c;整车要求空载跑到8m/s&#xff0c;电机转速已经拉到额定点&#xff0c;驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令&#xff0c;转速纹丝不动&#xff0c;三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年&#xff0c;用它写过嵌入式驱动、做过游戏服务端&#xff0c;也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词&#xff0c;我其实挺感慨的——这门语言的难点从来不是语法多&#xff0c;而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书以PDF格式发布&#xff0c;适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开&#xff0c;先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能&#xff0c;再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历&#xff1a;翻开源码注释、论文摘要或者一份专业报告&#xff0c;每个字都认识&#xff0c;连在一起却像天书。想找人聊聊&#xff0c;身边要么没人懂&#xff0c;要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型&#xff0c;几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 16:24:48 阅读更多 →