7.30 Agent八股文+RAG八股+小程序学习
目录
在工程实践中,为什么有时候选择「手搓」Agent,而不是直接用成熟框架?
讲讲 Agent 的反思机制?为什么要用反思?具体怎么实现?
什么是 RAG?详细描述一个完整 RAG 系统的详细工作流程?
相比直接微调 LLM,RAG 解决了什么问题?微调和 RAG 各自的优劣势是什么?
RAG 中的文档是怎么存的?粒度是多大?详细说说文档切割(Chunking)策略?
在 RAG 中 Embedding 究竟是什么?如何选择和评估一个 Embedding 模型?
Agent八股文
Agent 记忆压缩通常有哪些方法?
Agent 做记忆压缩,本质就是解决 LLM 上下文窗口容量有限、对话越长调用成本越高的问题,主流一共四种核心方案,项目里大多会组合搭配使用,我逐个说一下。
第一种是滑动窗口,也是最简单粗暴的一种。逻辑就是只留存最近 N 轮完整对话,历史消息超限就从最早的内容直接删掉。优点是代码好写,不用额外调用模型,零多余开销;缺点就是纯按时间一刀切,重要决策和闲聊内容待遇一样,很容易丢失早期关键约定,只适合短对话场景,我一般叫它金鱼式记忆。实际开发最常用的搭配就是滑动窗口 + 摘要一起使用。
第二种是摘要压缩,刚好弥补滑动窗口硬删除的缺陷。不会直接丢掉老旧对话,而是先用模型把快要溢出的历史内容提炼成一段精简摘要,用摘要替换原文放进上下文,近期新鲜对话依旧保留原文。不过这种方式会丢失不少细节,模型总结时会自主取舍内容,部分当时不起眼、后续要用的信息就找不回来了。它还有进阶玩法叫层级式摘要,远近对话分层压缩,近期原文、中期精简摘要、远期极致浓缩,就和公司会议纪要逻辑一致,远期只留核心决策,兼顾细节和历史脉络。我平时做项目最常用的组合就是滑动窗口搭配摘要,窗口快要淘汰旧数据前先做摘要兜底,最大程度保住关键信息。另外还有主动压缩,不等上下文爆满再处理,Agent 每次调用工具拿到冗长原始数据后,当场压缩精简,从源头控制 token 上涨,工具调用频繁的 Agent 特别适配。
第三种是重要性过滤,前面两种都是顺着时间线处理数据,这个方案是抛开时间,按照内容实际价值筛选记忆。做法是给每一段对话打分,低于阈值的内容直接剔除,高分重要内容保留。打分分两种,规则匹配打分成本低、速度快,但判断容易出错;调用模型逐条打分准确度更高,不过会增加接口开销,一般批量清理历史时使用。它还有一个延伸机制叫观察遮蔽,不会真正删除低价值内容,只会在构建请求 Prompt 时,根据当下任务隐藏无关历史,切换工作阶段再放开对应内容,不会造成不可逆的信息损耗。
第四种是结构化抽取,跟前三种依托对话文本存储记忆的思路不一样。我们其实没必要留存全部聊天原文,对话里真正值钱的是事实、项目状态、用户偏好、敲定方案这类信息。我们提前定义好业务字段,把关键信息抽取出来用结构化格式存储,类似医生整理病历档案,信息密度极高、几乎不会丢失核心数据。但缺点是开发成本最高,需要深度理解业务去设计字段,通用性比较差。
简单总结下四类方案的定位:滑动窗口、摘要压缩解决「对话过长该如何截断」的问题;重要性过滤解决「内容价值不均该如何筛选」;结构化抽取是更换存储载体,用更高密度的格式存放记忆,四种方案完全可以互相叠加组合落地。
除此之外还有一个配套优化手段Prompt 缓存,它不属于信息层面的记忆压缩,是计算层的优化。LLM 每次请求都要对全部输入 token 做预计算,像系统提示词、长期固定记忆这类不变的前缀内容,多次请求会重复计算,缓存可以复用这部分计算结果,大幅降低耗时与计费成本。记忆压缩管控放进上下文的数据体量,Prompt 缓存降低这批数据的计算开销,二者是互补关系,不会互相替代,高频长文本的 Agent 服务里搭配使用性价比很高。
落地选型上也有很清晰的标准:简单短对话单用滑动窗口就行;绝大多数常规长对话,滑动窗口加摘要是最稳妥通用的方案;业务有固定明确的核心字段,优先上结构化抽取;系统调用量大、对成本敏感,一定要接入 Prompt 缓存优化开销。
在工程实践中,为什么有时候选择「手搓」Agent,而不是直接用成熟框架?
成熟框架肯定有它不可替代的价值,像 LangChain 这类框架,封装好了工具注册、ReAct 执行循环、记忆管理、回调埋点整套能力,在项目 POC 验证想法、快速跑通流程的时候,能省下大量重复样板代码,几天的工作量压缩到很短时间就能完成,前期开发效率特别高。但项目走到上线生产、流量变大、业务逻辑高度定制化阶段,框架通用化的设计就会变成负担,这也是我们工程里部分核心逻辑选择手搓实现的根本原因,主要分为三大痛点。
第一点,多层抽象封装,线上故障排查成本极高。咱们实际开发应该都遇到过,自己写的业务代码只有几十行,一旦报错,堆栈动辄几十层,绝大多数链路全是框架内部源码。出问题时很难区分 bug 是我们自身代码、prompt 书写问题,还是框架内部逻辑、回调触发时机异常导致的,只能硬啃框架源码或者去翻官方 issue 定位问题,排查效率很低。哪怕搭配 LangSmith 做链路追踪,只能看到调用记录,解决不了框架黑盒带来的底层定位难题,对比手搓代码就很直观,手写的 ReAct 循环、消息拼接、工具执行逻辑全都明明白白,任意位置都能自定义打印日志、埋监控、打断点,整个调用链路完全透明,故障根因一眼就能锁定。
第二点,框架版本迭代容易产生破坏性变更,威胁线上稳定性。LangChain 早期大版本迭代很频繁,经常出现接口废弃、类结构重构的情况,就像旧版 AgentExecutor 已经被官方弃用,推荐迁移 LangGraph。线上项目稳定运行后,单纯做依赖升级就有可能直接引发服务报错,要么回滚版本,要么大面积修改业务代码做兼容,长期绑定第三方框架,线上稳定性会被外部迭代节奏裹挟,不敢轻易更新依赖,久而久之堆积技术债。反观手搓代码,底层只依赖稳定的 LLM 原生 SDK,代码结构、接口全由自己把控,不会被外部的 breaking change 影响,线上运行更加稳固。
第三点,通用化设计自带隐性开销,定制改造成本高昂。框架为了适配各行各业的通用场景,内置了大量默认逻辑,比如自动序列化各类中间数据、全局回调事件、全量日志采集。很多逻辑在我们的业务里完全用不上,但每次 LLM 请求、工具调用都会无脑执行,流量上涨之后,多余的计算不仅拉高接口延迟,还会带来不必要的 token 成本开销。如果想要删减这些冗余逻辑、贴合自身业务做定制改造,需要层层继承、重写框架多层父类方法,改造难度往往比从零手写还要麻烦。手搓就不存在这个问题,我们只编写业务刚需的代码逻辑,按需增加重试、异常捕获、记忆处理逻辑,没有多余负担,性能优化的自由度完全掌握在自己手里。
结合工程落地经验,手搓并不是全盘抛弃框架,也不是框架一定不好,二者并不是二选一的关系,行业里最务实的方案是核心逻辑手写,周边能力复用框架。Agent 最核心的 ReAct 循环、对话消息管理、工具调度、异常重试、任务状态管控这些核心心脏模块,直接手搓,保证可控、易排查、易优化;而文档解析、向量库客户端、LangSmith 链路追踪这类纯工具属性的外围功能,直接使用框架现成能力,不用重复造轮子,兼顾开发效率与线上稳定性。
另外 Anthropic 官方文档其实也提倡这个思路,优先用极简原生逻辑跑通核心流程,不要一上来重度依赖框架。很多项目的迭代路径基本都是先用框架快速验证方案,上线踩坑之后逐步把故障高发、性能敏感的核心模块替换为手写代码,框架最后只保留配套工具能力。最后我个人觉得,框架本身没有问题,真正的隐患是在没有吃透框架底层原理的前提下,盲目重度依赖框架,这才是生产环境最大的风险。
如何赋予 LLM 规划能力?
想要给 LLM 赋予规划能力,本质就是把模型原本隐式、一次性的 token 生成推理过程显性化,避免长链路推理出现误差累积、思路跑偏的问题。行业里从基础到进阶依次是 CoT、ToT、GoT 三套推理结构方案,除此之外在真实 Agent 工程开发中,最落地好用的其实是 Plan-and-Execute 先规划后执行的架构,我逐层拆开来讲。
先说最基础的CoT 思维链,也是项目里使用率最高的方案。实现方式非常简单,Zero-shot 只需要在提示词加上 “请一步步拆解思考,分步输出推理过程”;想要稳定性更强就用 Few-shot CoT,附上几份带完整推导步骤的示例让模型模仿输出格式。 它的原理就跟我们笔算数学题一样,把思考过程落在上下文里,约束模型不跳步、不凭空脑补答案。但它的短板十分致命:全程只有单条线性推理链路,只要最开始的思考方向出错,整条推理链条都会全部失效,没有任何纠错、回头修正的机会。优势就是改一句 Prompt 就能接入,没有额外的 LLM 调用成本,属于零开销的基础规划手段。
为了解决 CoT 单链路易翻车的问题,就衍生出了ToT 思维树。它不再只生成一条思路,会同步产出多条初始推理分支,整体遵循「生成多条候选思路→打分评估每条路径可行性→保留高分路径深入推演、裁剪劣质分支」的循环流程,在探索过程中实时择优,走错方向也能及时舍弃错误分支。 不过对应的代价很高,常规配置每层 3 条路径、2~3 层深度的情况下,LLM 调用成本是 CoT 的 3 到 5 倍,深度更高、分支更多时开销会成倍上涨。一般只用在高精度数学推理、复杂逻辑推演这类对正确率要求极高的场景。
在 ToT 树形结构的基础上继续迭代,就是GoT 思维图。树形结构各个分支互相独立,不同路径产出的中间结果没办法互通、合并使用,而图结构支持多个前置推理节点汇聚数据,一个中间结论可以供给多条后续推理使用。举个例子,分别分析两份竞品再合并做对比总结,用 GoT 就能自然把两份分析结果汇总到同一个节点处理,更贴合人类处理复杂复合任务的思考习惯。但这套方案目前大多停留在学术研究层面,搭建复杂度很高,线上生产环境基本不会落地使用。 三者的迭代逻辑很清晰:CoT 实现推理显性化,ToT 解决思路出错无法挽回的问题,GoT 解决多路径中间成果无法复用的痛点。
上面三种偏向推理层面的规划优化,而我们做业务 Agent 开发,真正落地的标准方案是Plan-and-Execute(规划执行架构)。核心思想就是将整个任务拆成规划、执行两个阶段,还配套动态重规划机制。第一步由规划器把复杂大任务拆解成有序的分步执行清单;第二步执行器依照清单,配合工具调用一步步落地执行任务;每完成一个步骤都会校验进度,依靠重规划器结合最新的实际执行结果,动态修改后续计划,适配任务过程里出现的新信息、异常情况。 这里区分一下它和 ReAct 的关系:ReAct 是单步内思考 - 行动 - 观测的闭环,属于即时的局部决策;Plan-and-Execute 提供全局的任务排布框架,二者属于互补关系,项目里经常搭配使用。同时这个架构还能做成本优化,规划步骤使用强能力大模型保障整体方向不出错,执行步骤换成轻量化低成本模型控制开销,LangGraph 框架也原生支持这套开发模式。
最后结合工程落地做一下整体取舍总结:日常普通任务直接用 CoT 足够,成本最低、接入最简单;高精密推理需求酌情使用 ToT,提前评估接口费用与延迟开销;GoT 只需要了解原理即可,不用强行落地;开发正式的工具调用 Agent 系统,优先采用 Plan-and-Execute 架构做整体任务规划,再搭配 CoT 完善单步内部推理,是最稳妥、实用的组合方案。
讲讲 Agent 的反思机制?为什么要用反思?具体怎么实现?
首先,什么是反思、为什么要用反思。简单来讲,反思就是给 Agent 增加一套自检闭环,Agent 完成推理、工具调用、内容生成之后,主动评估自身输出质量,存在问题就针对性修正优化,并不是单纯随机重试一遍。 LLM 单次生成很容易出现逻辑断层、关键信息遗漏、事实幻觉、表述模糊这些问题,模型一次性输出没办法自查纠错,加入反思就跟人写完文档自我校对、做完方案复盘一样,能大幅度提升最终结果准确度。但反思是有成本的,每一轮反思都会额外调用 LLM,拉高 token 开销和接口延迟,所以我项目里不会全流程开启,只会在报告编写、代码生成、重要推理这类高要求的关键节点启用。
然后是最基础的实现方案,底层依托 Self-Refine 这套「生成→评估→改进」闭环,整套流程靠两份 Prompt 配合循环执行。 第一份是评估 Prompt,这块有两个硬性设计要点,缺一不可。第一必须给出明确的校验维度,划定事实正误、逻辑完整性、内容覆盖率、语句通顺度这些审查方向,如果笼统让模型自查,它大多会判定输出合格,找不出真实漏洞;第二一定要设置PASS 退出标识,给模型一个达标终止的出口,不然模型会刻意鸡蛋里挑骨头,不断修改原本正确的内容,越改偏差越大。 如果评估结果不是 PASS,就执行第二步改进流程,改进 Prompt 必须同时传入原始任务、初次输出内容、评估整改意见三样数据,三者缺一不可,模型才能精准对应问题定点修改,不会全盘重写造成内容跑偏。 整体代码层面就是一层普通 for 循环,配合 LLM 调用完成迭代,不过光靠模型自主 PASS 终止不靠谱,工程上必须硬性配置最大反思轮次,一般限定 2~3 轮,杜绝无限循环空耗资源。
按照执行粒度划分,反思分为步骤级反思和任务级反思两种落地形式。 步骤级是每一次工具调用、单步推理结束立刻校验纠错,优势是小问题当场修复,不会顺着错误逻辑持续执行,避免前面全部工作作废,适合多步骤强耦合的检索、计算类任务;缺点是每一步都新增 LLM 调用,整体耗时和成本涨幅很高。 任务级是整套任务全部跑完之后统一全局复盘校验,开销更低,只多出一次模型调用,还能发现分步全都正确、但整体前后矛盾、内容衔接断裂这类整体性问题,普遍用在调研报告、文案撰写这类步骤相对独立、看重最终成品质量的场景;弊端就是前期步骤出错,要等到最后才能发现,中间资源全部浪费。
在基础自我反思之上,效果更好的方案是多 Agent 互评机制,单独搭建一个 Critic 评审 Agent 专门负责校验产出。类比我们写代码自己自查很容易忽略漏洞,交给同事 Code Review 更容易发现问题,同一个模型生成内容时已经形成固定的自洽逻辑,自检时会下意识包容自身错误,独立的评审 Agent 没有固有思维,审查视角更客观,漏洞识别率更高。一般代码校验、高精度数据分析场景会使用互评,代价是系统复杂度、调用成本同步上升,普通业务场景只用单 Agent 自反思就够用。
除此之外还有几个进阶的反思理论方案,面试里提出来会更加分。第一个是 Reflexion,它不止单次整改当前输出,还会把本次出错的教训、避坑要点存入记忆,后续同类任务会读取这份经验,规避重复踩坑,很适合代码生成这类高频重复的任务。第二个是 LATS,把反思和蒙特卡洛树搜索、思维树结合,多条执行路径同步探索,结合每一条路径的反思反馈优化后续搜索方向,不过这套方案成本很高,目前大多停留在学术研究,线上生产很少落地。还有辩论式反思,设置正方、反方两个 Agent 对抗校验,反方专门挖掘方案漏洞,对抗形式可以挖出更深层的问题,只用在商业方案、法律文书这种极高标准的审核场景。
最后结合实际工程落地,聊聊整体的权衡思路。简单问答、格式转换、高实时性需求的接口,完全没必要接入反思,只会徒增成本;只有错误代价高、内容精度要求严苛的环节才开启。同时永远以固定轮次作为兜底终止条件,不能信任模型自主停止迭代。总的来说反思是提升 Agent 可靠性的有效手段,但属于锦上添花的优化功能,一定要按需使用,把控好质量、延迟、成本三者的平衡。
如何设计多 Agent 的协作与动态切换机制?
我会分成两大块来讲,一块是多个 Agent 之间的数据协作通信方案,另一块是 Agent 流转的路由切换机制,同时结合实际工程落地的选型策略一起说明。
首先先说多 Agent 的协作通信,主流分为消息传递和共享状态两种方案,二者适用场景、优缺点差异很大。 第一种是消息传递模式,类比公司部门之间收发邮件,Agent 处理完成后将结果投递到消息队列,下游 Agent 只订阅自身需要的消息进行消费。最大核心优势就是彻底解耦,发送方不用关心接收方是谁、有多少个接收服务,接收方也不用感知数据来源,各 Agent 可以独立部署、独立扩缩容。缺点是需要额外维护消息中间件,架构复杂度会拉高。适合 Agent 彼此耦合弱、支持并行执行、服务需要拆分部署的场景。
第二种就是共享状态机制,最典型的落地就是 LangGraph 的全局 State 结构,好比所有 Agent 共用一块共享白板,全局统一存储任务原始需求、全流程进度、各个节点产出数据,每个 Agent 执行完毕只会增量写入自身结果,后续节点直接读取状态数据即可完成数据流转。优点是对接简单、开发成本低,天然适配流水线式有序执行的多 Agent 流程;但要做好状态的规范化设计,不然极易出现数据覆盖、脏数据问题。 我在落地共享状态时会定下三个规范:一是状态分层,区分全局公共状态和各个 Agent 私有局部状态,私有数据不会污染全局空间;二是写入遵循只追加、不覆盖的原则,依靠框架做增量合并更新;三是执行报错时将错误信息写入状态,调度器可以依据异常状态做重试、终止等处理。 选型上:强前后依赖的流水线任务优先共享状态;追求服务解耦、多 Agent 独立运行的分布式架构,就选用消息传递。整体多 Agent 协作架构又分为流水线、层级调度、自主协商三类,项目里一般都是混合搭配使用。
其次讲 Agent 的切换路由工作,整体由调度器 Orchestrator 负责管控流转,分为静态路由、LLM 驱动的动态路由,还有去中心化的 Handoff 交接模式。 静态路由就是提前硬编码流转规则,根据任务阶段、数据标识固定指定下一个执行 Agent。优势是没有额外 LLM 调用开销,系统行为可控、便于排查 bug,稳定性极强;短板是只能处理预先设计好的流程路径,未知的异常场景无法适配。 动态路由交由 LLM 读取上下文、当前任务进度、可用 Agent 列表,自主判定下一步执行对象。优势是灵活性拉满,各类非常规、边界场景都能自适应处理;弊端十分明显,每次路由判定都会增加一次 LLM 调用,拉高耗时与 token 成本,并且模型存在路由判断失误的概率,整体系统行为不可预测。 单纯只用任意一种都有缺陷,我实际项目里采用混合方案:主干业务流程全部使用静态路由兜底,保障绝大多数场景稳定运行;只有静态规则匹配不到的异常、边缘分支,才交给 LLM 动态路由做兜底处理,平衡稳定性和灵活性。
另外还有 Swarm 框架主推的 Handoff 交接模式,属于去中心化流转,不存在统一的中央调度器,由当前执行的 Agent 自己判断任务是否完成、自主把任务交接给对应 Agent,类似接力赛跑。好处是 Agent 熟知自身业务边界,交接判断更精准,不存在中心节点性能瓶颈;但缺少全局管控,极易出现 Agent 来回转交形成死循环。使用该模式必须严格划定每个 Agent 的职责范围,并且记录流转轨迹,靠重复校验机制规避循环问题。这种方式更适合 Agent 数量少、业务流向简单的系统,复杂大型多 Agent 系统还是中心化 Orchestrator 调度更加稳妥。
最后整体总结我的工程落地思路:流程有序、步骤依赖强的业务,用 LangGraph 共享状态做数据传输;分布式、高隔离需求选用消息队列的消息传递。路由层面主干静态规则兜底,异常走 LLM 动态路由;简单小体量多 Agent 可用 Handoff 去中心化交接,复杂系统优先中心化调度,同时配套状态防护、路由日志、死循环拦截等兜底策略,兼顾可用性、稳定性与开发成本。
RAG八股
什么是 RAG?详细描述一个完整 RAG 系统的详细工作流程?
首先 RAG 全称是检索增强生成,核心作用就是解决 LLM 天生的知识冻结问题。大模型训练完成之后,参数里的知识就固定死了,训练截止时间之后的新闻、公司内部私有文档、业务资料,模型全都没办法知晓。如果用微调补齐新知识,不仅算力、时间成本很高,后续资料更新还得重新训练模型,改动成本巨大。 而 RAG 不走修改模型权重这条路,相当于给 LLM 安排一场开卷考试,用户提问的时候,实时去外部知识库检索匹配的参考资料,把检索出来的上下文连同用户问题一起喂给大模型,模型依托给到的真实资料作答,从根源缓解模型幻觉,还能读取私有、实时数据。
一套标准可用的 RAG 系统,严格分成离线建库阶段和在线实时问答阶段,离线一次性执行构建知识库,用户提问才会触发整条在线链路。
先说离线阶段,主要分为五步:文档加载、文本分片 Chunking、Embedding 向量化、向量粗排入库、向量数据库持久存储。 第一步文档加载,借助 LangChain、LlamaIndex 自带的各类加载器,读取 PDF、Word、markdown、网页、数据库数据等各式各样的原始文件。 第二步 Chunk 文本切割,不会直接把整篇文档向量化存储。一方面 Embedding 模型有固定的 token 输入上限,全文无法一次性送入;另一方面整片文本生成的向量会稀释细节语义,检索精度大打折扣。工程常规取值是单块 Chunk 控制在 500~1000token,相邻分片设置 100token 左右的重叠区,防止一段完整语义被硬生生拆分断裂。 第三步 Embedding 向量化,把文本片段转化为高维数字向量。原理就是 Embedding 模型依靠对比学习训练,搭建出一套语义坐标系,语义相近的文本向量在空间距离更近,无关文本距离更远。它匹配的是语义含义,不是死板的关键词匹配,也是整个语义检索的底层基础。 第四步粗排 + Rerank 精排校验,粗排依靠向量相似度计算召回 Top-K 候选片段,但单纯向量距离容易出现语义不符的脏数据;所以搭配 Cross-Encoder 结构的重排模型,深度匹配问题和文档真实相关性,过滤无效内容,一般粗排取 Top20,精排筛选保留 3~5 条最优片段。 第五步数据入库,将 Chunk 原文和对应向量一同存入向量数据库,常用的有 Milvus、Qdrant、Chroma 这类组件,专门优化高维向量的存储与高速相似度检索,百万级向量也能做到毫秒级查询响应。离线流程做完之后,知识库就永久就绪了。
再讲用户提问触发的在线执行链路,依次是 Query 优化改写、向量粗检索、Rerank 精筛、Prompt 组装、LLM 生成回复。 第一是 Query 改写优化,用户提问经常存在口语化、指代模糊、缺少上下文的问题,直接检索效果很差。会调用 LLM 结合对话历史,把模糊问句规整成语义清晰、适配检索的标准查询语句。 第二把处理后的 Query 进行向量化,到向量库做相似度匹配,粗召回一批相关文档片段。 第三执行 Rerank 精排,剔除看似向量相近、实际无关的内容,保留相关性最高的片段。 第四拼接 Prompt 模板,将用户原始问题 + 精排后的参考资料封装送入大模型,同时在提示词中约束模型:只能依托给到的资料作答,没有对应信息就如实告知,极大压制模型凭空编造内容的幻觉问题。 最后由 LLM 结合参考上下文,输出合规、有据可依的回答内容。
整体对比微调方案,RAG 有两个不可替代的核心价值:第一知识库内容可以随时新增、修改、删除,做到知识热更新,不用重新训练模型,运维成本很低;第二每一段回答都能溯源对应的原始文档片段,可排查、可校验,可解释性更强,也是企业内部知识库、客服问答系统最主流的落地方案。
大模型的 RAG 主要用来解决什么问题?
RAG 所有要解决的问题,根源都是大模型存在知识冻结这个固有缺陷。模型预训练完成之后,所有知识全部固化在参数权重里面,就像一本印刷完毕的百科全书,内容没法自主更新,由此衍生出三大核心痛点,RAG 就是从根源上处理这三类问题。
第一个痛点是知识时效性不足。每一款大模型的训练数据集都有明确的截止时间,截止日期之后的新闻、新品资料、财报数据、行业新规,模型本身完全不知情。就算它不清楚相关内容,也不会直白回复不知道,只会依靠过往数据规律推演内容,很容易输出错误信息。而且重训基座模型成本天价,频繁微调也不现实,没办法跟着外界信息同步迭代更新知识。
第二个痛点是企业私有知识完全空白。公网训练数据只会收录公开内容,公司内部的产品手册、客服制度、合同资料、内部业务方案这些私有文档,根本不会进入模型的训练语料。直接询问这类内部业务问题,模型没有任何参考依据,给出的回复基本都是凭空编造的内容,企业落地知识库问答、内部 AI 助手时,这是最普遍的难题。
第三个就是大家常提到的模型幻觉问题,这里一定要区分清楚,幻觉并不是一个独立 bug,它只是知识缺失带来的副产品。大模型的运行逻辑是逐 token 概率预测,生成文本的优先级高于承认未知。当参数里没有匹配的知识时,模型为了保证语句通顺连贯,就会拼凑出看似严谨、实际失真的答案。尤其是医疗、法律、金融这类严谨场景,编造的内容会带来实打实的业务风险,同时纯模型生成的回答没有任何资料来源,出错之后也没办法定位问题出处。
而 RAG 的解决思路,就是把知识和模型彻底解耦,不去改动模型的任何参数权重,把全部业务、实时知识外置存储在向量知识库。用户提问的瞬间,实时检索匹配的文档片段,把原文资料连同用户问题一起送入 Prompt,强制模型依托给到的真实资料作答,相当于给大模型开卷考试。 对应来看,知识库新增、修改文件立刻生效,完美搞定知识时效滞后问题;企业内部文档入库即可被检索读取,补齐私有知识盲区;模型有真实原文作为作答依据,幻觉发生率会大幅下降,同时每一句回答都能绑定对应的文档分片,方便溯源核查、问题定位。
总结来说,RAG 不只是单纯降低幻觉,它先是解决了知识冻结带来的时效、私有知识两大硬伤,幻觉只是顺带被改善的衍生问题,这也是目前企业落地 AI 应用,首选 RAG 方案的核心原因。
相比直接微调 LLM,RAG 解决了什么问题?微调和 RAG 各自的优劣势是什么?
首先,二者底层逻辑完全不同:微调是改模型参数,通过二次训练把知识、风格强行固化进模型权重里;而RAG完全不动模型参数,是在推理阶段实时从外部知识库检索资料,注入Prompt辅助回答。这也是RAG能解决微调诸多工程痛点的核心原因。
相比直接微调,RAG主要解决了微调的四大核心短板。
第一,解决了知识更新成本极高、迭代滞后的问题。微调的知识是永久冻结在模型里的,业务数据、行业规则、产品文档一旦更新,必须重新标注数据、租用GPU重新训练,耗时耗力,完全跟不上业务高频迭代的节奏。而RAG只需要更新向量知识库,新增、修改、删除文档即时生效,无需动模型,支持知识热更新。
第二,解决了答案黑盒、无法溯源的问题。微调后的模型回答完全依赖自身参数,输出结果没有任何依据,出错后根本无法定位是训练数据问题还是模型推理问题,可解释性极差。而RAG的每一次回答都能溯源到具体的文档片段,有据可查,便于问题排查和内容校验,非常适合企业合规场景。
第三,解决了微调容易引发灾难性遗忘、泛化变差的问题。频繁微调会让模型丢失原有通用能力,还容易因为训练数据偏差出现过拟合。RAG全程不改动模型,完全保留模型原生能力,只靠外部检索补充专属知识,不会破坏模型基础能力。
第四,大幅降低落地成本与门槛。微调需要高质量标注数据集、算力资源、训练调优经验,小团队很难落地;而RAG仅需Embedding模型+向量数据库,轻量化即可搭建,开发、运维成本极低。
接下来我分别说下两者的优劣势和对应的适用场景。
首先是微调(Fine-tuning)。它的优势很明确:第一,推理无额外检索步骤,响应延迟更低,接口性能更好;第二,能深度改造模型行为,专门优化模型的输出语气、固定格式、行业术语、对话风格,还能提升特定领域的推理熟练度;第三,无需依赖外部知识库,不占用上下文窗口,轻量化部署更简单。
但它的短板也非常致命:知识更新极不灵活、算力和数据成本高、输出不可溯源、迭代周期长,不适合高频变动的业务知识场景。
所以微调的核心适用场景,从来不用来补实时/私有知识,而是用来改造模型的「说话方式和行为习惯」。比如需要统一企业客服话术、固定报告输出格式、适配专属行业语气、优化特定任务推理能力,这些场景微调是最优解。
然后是RAG。它的优势刚好对应微调的短板:支持知识实时热更新、答案可溯源可校验、零模型改动、无遗忘风险、落地成本低,完美适配私有知识库、动态更新的业务问答场景,还能有效抑制模型幻觉。
但RAG也有明显劣势:第一,多了检索、重排链路,整体响应延迟更高;第二,检索质量决定回答上限,没召回的知识,模型再强也无法生成,优化重点只能在检索层而非生成层;第三,无法改变模型的输出风格和推理习惯,只能提供参考资料,对模型底层能力没有提升。
最后补充工程落地的核心思路:二者不是二选一的替代关系,而是互补组合使用,也是行业主流方案。简单说就是:微调解决「怎么说」,RAG解决「说什么」。
我们可以先通过微调,让模型适配行业语气、固定输出格式、掌握专属术语和推理范式,打好行为基础;再叠加RAG架构,实时注入最新、私有业务知识,保证回答内容准确、可溯源、可迭代。二者结合,既能让模型输出专业规范,又能保证知识实时准确,兼顾效果、成本和稳定性。
RAG 中的文档是怎么存的?粒度是多大?详细说说文档切割(Chunking)策略?
首先讲文档存入向量数据库的整套离线流程。原始的 PDF、Markdown、网页这类文件,绝对不能整篇直接入库。一方面 Embedding 模型本身有 token 输入上限,长文本根本没法一次性送入计算向量;另一方面整篇文档压缩成单个向量,各类混杂的信息会被平均化处理,后续检索只能匹配到整篇文档,定位不到用户真正需要的某一段内容,检索精度会特别差。 标准入库链路是:原始文档加载 → 执行 Chunk 文本切分 → 每一个文本块单独做 Embedding 向量化 → 把数据写入向量库保存。 向量库里的单条数据一共由三部分组成,缺一不可:第一是高维向量,专门用来做相似度匹配、完成检索定位;第二是 Chunk 原始文本,检索命中之后,真正塞进 Prompt 交给大模型阅读使用;第三是元数据 metadata,记录文件名称、页码、章节、文件类型这些信息,用来做检索过滤和答案溯源。简单概括就是向量负责 “找得到”,原文负责 “读得懂”,元数据负责 “查来源”。
然后是 Chunk 的基础粒度选择,没有万能固定数值,行业通用的基准区间是500~1000token,这个区间是平衡语义完整度和检索精准度的折中起点。切得太小,比如几十 token,句子、语义被拆分破碎,大模型拿到碎片内容没办法理解完整含义;切得过大,单块信息冗余杂乱,向量语义模糊,容易召回大量无关内容。就算选定基础 token 大小,正常都会搭配一定的文本重叠值,一般设置 100token 左右,规避刚好在一句话中间截断文本的问题。
接下来详细说下实际开发里常用的五类 Chunk 切割方案,从基础到高阶依次说明: 1、固定大小 + 重叠切块,这是最基础的兜底方案,按照设定好的 token 长度一刀切文本,依靠相邻区块重叠内容弥补语义断裂的问题。优点是代码实现最简单,可控性强;缺点很明显,不会识别文本天然语义断点,很容易切断完整语句、段落。一般只用在没有规整格式的纯零散文本场景。 2、语义 & 结构化边界切块,不会死板按长度切割,顺着文档天然的分隔点拆分,优先按照段落、句号、标题层级划分区块,Markdown、带层级标题的手册文档用这个最合适。每个 Chunk 都是独立完整的话题内容,语义连贯性最好,检索质量更高,缺点需要做分隔符优先级适配,开发成本比固定切块高一点。针对表格这类特殊内容,需要整体整块保存转成 Markdown 格式,不能按行拆分,防止表头和内容脱节;代码文件要用 AST 语法树解析,以函数、类作为最小切块单元,保住代码逻辑的完整性。 3、父子切块(Parent-Child),工程里提升检索效果很常用的高阶方案,完美解决 “小块精准但缺上下文,大块上下文全但检索不准” 的矛盾。入库时一份内容存两份,细粒度的子 Chunk 用来生成向量做检索匹配,粗粒度、包含完整上下文的父 Chunk 通过 ID 和子块绑定。检索时靠小子块精准定位内容,最终把对应的父块完整文本传给 LLM 生成回答。代价是存储资源翻倍,索引维护复杂,适合知识库、政务、法律这类对回答准确度要求高的系统。
4、Late Chunking 延迟切块,属于比较新的优化思路,和传统先切块、再编码的顺序反过来。先用支持超长上下文的 Embedding 模型读取整篇全文,计算出所有 token 的向量,全文的语义信息会依靠注意力机制互相传递,之后再按照区块范围合并 token 向量得到 Chunk 向量。这么做每个文本块天生自带整篇文档的全局语境,大幅缓解切块丢失上下文的问题;短板是对 Embedding 模型上下文窗口要求高,向量计算的资源开销更大。
最后结合项目落地说下我的实际搭配方式:普通纯文本用固定大小加重叠保底,结构化文档、产品手册用语义标题切块处理;正式生产环境、高精度问答需求,直接叠加父子切块优化召回效果。不同策略按需选用,不会单一使用某一种切块方案。
怎么规避语义被切割掉的问题?
首先我先说下语义切断的本质问题:不是文本内容丢了,是一整段完整语义被拆分到不同分片里,单个 chunk 语义残缺、向量相似度变低,检索的时候两边全都召回失败。处理方案整体分成两大思路,一类是切片阶段就避免斩断语义,属于事前预防;另一类是切片已经切开了,靠检索策略补齐上下文,属于事后补救,另外还有进阶的 Contextual Retrieval 从向量根源优化,我逐个说明。
先说第一类,事前切割优化方案。 第一个就是大家最先用到的固定分片 + Overlap 重叠,这只是最低限度的兜底手段。相邻 chunk 留出一段重叠文本,能防止边界处的短句直接丢失,但解决不了一句话、一整条业务语义被对半劈开的情况。就像企业客服那条规则被拆分,重叠没法把两段残缺内容的语义关联起来,只能当作基础配置,不能当做核心解决方案。 第二个是语义边界切割,这是最常用的治本切割方式。借助 NLP 工具识别句号、段落、标题这些文本天然断点,以完整句子、完整段落为最小单元去填充 chunk 容量,绝不强行从语句中间切割。像 Markdown 手册、规整的文档,直接依照标题层级划分分片,每个 chunk 都是独立完整的话题。如果是代码、表格这类特殊内容,代码依靠 AST 解析以函数、类切割,表格整体封装成一个 chunk,从源头就保证每份文本语义闭环。 第三个是命题化切割,属于高精度场景的方案。不靠文本位置分片,调用大模型把原文拆解成一条条独立完整的事实命题,每一条命题单独拿出来不用依托上下文就能读懂,向量的语义纯度最高。缺点就是持续调用 LLM 会增加成本,一般只用在合同、法律、医疗这类高严谨性知识库。
然后是第二类,事后检索补偿方案,就算分片切散了,检索环节把上下文补齐。 第一种是句子窗口检索,入库时拆成单个句子做向量用于精准检索;命中目标句子之后,不会只返回这一句话,而是取出该句子前后若干句组成窗口上下文交给大模型阅读,补齐缺失的语境。实现简单,不需要额外存储关联数据,轻量化项目很适用。 第二种是父子切割方案,工程落地里均衡效果和成本的主流选择。同一份原文存储两份数据,细粒度的子 chunk 用来做向量检索,检索精准度高;粗粒度、包裹完整上下文的父 chunk 通过 ID 和子块绑定匹配。检索依靠小子块定位,最终返回完整父块内容给 LLM 生成回答。弊端是存储量翻倍,索引关系需要维护,换取的上下文完整性提升很明显。
最后讲业界现在效果很突出的Contextual Retrieval,Anthropic 提出的方案。它不改动 chunk 原文结构,在向量化之前,让大模型通读整篇文档,为每一个 chunk 生成一段背景描述,写明这段文字在全文里的位置、对应的主题,把这段描述拼接在 chunk 前方再去做 Embedding。原本孤立的片段自带全局语境,向量编码就包含了完整语义信息,大幅降低分片割裂带来的检索失效问题。很多人担心逐个 chunk 调用 LLM 开销太大,实际可以搭配 Prompt Caching,整篇文档作为固定前缀缓存下来,后续同文档所有分片的调用复用缓存内容,整体调用成本能下降八九成,成本完全可控,官方数据搭配混合检索能明显降低召回失败率。
最后结合项目落地说我的实际选型搭配:日常开发用「语义边界切割 + 适度 overlap」作为基础标配;普通知识库优化叠加父子切割;对答案准确度、召回率要求严苛的业务系统,再加 Contextual Retrieval 做补强,多层方案配合使用,就能很好解决语义被切断的各类问题。
在 RAG 中 Embedding 究竟是什么?如何选择和评估一个 Embedding 模型?
首先说 RAG 里 Embedding 到底是什么。它不只是简单把文字转成一串数字向量,本质是对文本做语义压缩表征,不管输入一段话多长,都会输出一串固定长度的浮点数数组。它最核心、支撑整个向量检索的特性就是:两段文字语义越相近,映射出来的向量在高维空间里夹角就越小,余弦相似度数值就越靠近 1,系统就是靠着这个特性做语义匹配,区别于老旧的关键词检索。 计算相似度我们统一用余弦相似度,不用直线距离。原因很直白,向量的长短会被文本篇幅、用词多少干扰,余弦只关注向量的指向方向,只评判意思是否一致。举个例子,“iPhone 截屏” 和 “苹果手机截图” 字面词汇完全不一样,关键词搜不到,但语义一致,向量方向高度重合就能匹配上;而 “苹果手机” 和 “苹果果汁” 都带有苹果二字,字面重合度高,但语义无关,向量距离会拉开,这也是语义检索的核心优势。文档入库、用户提问这两处文本,都会经过 Embedding 转为向量,向量库依靠相似度匹配召回相关 chunk,是整个 RAG 检索层的地基。
第二部分讲讲 Embedding 模型该怎么选型,不会直接无脑选用 OpenAI 接口,也不会只盯着榜单第一名,结合业务实际从四个维度综合判断: 1、语种匹配:纯中文知识库,优先智源 BGE 系列,bge-large-zh-v1.5 是行业经典选型,适配国内文档;中英文混杂的场景选用 bge-m3,同时支持稠密、稀疏、多向量三种检索模式;新项目也可以选用阿里开源的 Qwen3-Embedding,中文效果更强;只有纯英文业务、开发节奏快的场景,才考虑 OpenAI 的 text-embedding 系列,OpenAI 模型中文适配性其实一般,还存在数据出境、调用计费的合规成本问题。 2、数据合规要求:企业内部私密资料、政务、医疗数据不允许外传,就必须选用 BGE、Qwen 这类开源模型本地部署;无数据敏感问题,才可以选用各家闭源 API 服务。 3、向量维度取舍:维度越高语义表征越精细,检索精度更高,但向量存储占用、检索耗时都会上涨。百万级文档知识库,1024 维是性价比平衡点;体量很小的演示项目可用 1536 维;像 OpenAI 的嵌入模型支持向量降维,能够灵活平衡精度和存储开销。 4、模型上下文窗口:窗口大小决定单次可处理的 chunk 长度,长文档拆分后的大块文本,就得挑选支持超长输入的嵌入模型。
顺带说下市面主流模型的适配场景:OpenAI 嵌入模型胜在开箱即用,英文表现优秀;BGE 是国内项目落地最普遍的兜底开源方案;Qwen3-Embedding 是新一代中文强力选择;Voyage、Cohere 这类闭源模型更偏向海外高精度英文检索场景。
第三点,也是最关键的,模型不能只依靠 MTEB 排行榜挑选,要落地实测评估。MTEB 只是通用数据集测出的分数,通用文本的数据分布和我们手里的医疗、法务、企业客服这类专业业务数据差别很大,榜单第一的模型放到自己业务里未必好用,参考价值有限。 真正靠谱的评估方式,是基于自身业务数据搭建测试集,整理一批「用户提问 + 标准对应 chunk」的配对数据,把候选模型全部跑一遍,核心观测Hit@K 指标。Hit@5=0.8,代表 80% 的业务问题,对应的正确文档片段都能出现在检索结果前五条里,一般 Hit@5 低于 0.7,就要排查嵌入模型或者 chunk 切割策略是否存在问题。只有基于真实业务数据跑出的量化指标,才具备实际参考意义。
结合我自己的项目落地经验,常规中文内部知识库,优先本地部署 bge-large-zh-v1.5,再用自有业务数据跑完 Hit@5 测试,指标达标再正式投入使用;中英文混合场景切换 bge-m3;对检索精度要求严苛的系统,会测试对比 Qwen3-Embedding 择优选用,全程不会仅凭榜单或者网传效果直接敲定模型。
Embedding 有哪几种算法你了解过吗?
面试官,文本 Embedding 算法整体分为三代逐步迭代升级,每一代都是为了解决上一代遗留的短板,我顺着发展顺序逐一说明,同时结合 RAG 场景讲清楚每一类的适用与缺陷。
第一代:静态词向量,代表 Word2Vec、GloVe、FastText
这是最早一批落地的词嵌入方案,核心逻辑依靠词语周边的词汇共现关系训练出固定词向量。Word2Vec 分为 CBOW 和 Skip-Gram 两种训练模式,CBOW 用上下文词汇预测中心词,训练收敛速度快;Skip-Gram 反过来用中心词预测周边词汇,对低频生僻词适配更好,项目里一般默认选用 Skip-Gram。GloVe 额外结合了全局语料的共现矩阵做分解计算,全局语义捕捉比 Word2Vec 更均衡。FastText 在词的基础上拆分出子词 n-gram 结构,能处理训练集里没出现过的新词、专业名词,弥补了 Word2Vec 无法识别未登录词的问题。
但这类模型最大的硬伤就是静态绑定,一个词语永久对应唯一向量,完全区分不开多义词。就像 “苹果” 代表水果和手机品牌时,向量一模一样,语义区分失效;而且它只做到了单个词语向量化,没办法直接表征完整句子,完全没办法满足 RAG 的语义检索需求,现在基本不会用在 RAG 系统里。
第二代:上下文动态向量,代表 ELMo、原生 BERT
为了解决一词多义的痛点,第二代模型可以根据句子语境,动态生成词语向量。ELMo 依靠双向 LSTM 分别正向、反向读取文本,拼接两层结果得到上下文向量,是第一款大规模落地的动态嵌入模型。而 BERT 改用 Transformer 结构搭配 MLM 掩码预训练任务,双向同时读取全文上下文,语义理解能力直接碾压 ELMo,也是 NLP 领域的里程碑模型。
不过原生 BERT 并不适合拿来做 RAG 检索。它比对两段文本相似度的时候,必须把查询语句和库内文档两两拼接送入模型计算。如果知识库有上百万条文档,一次检索就要执行上百万次 BERT 推理,耗时极高,线上查询延迟完全没法接受。简单来讲,BERT 做分类、抽取任务很强,但原生结构天生不适配大规模的实时向量检索场景。
第三代:面向检索优化的句子级对比学习 Embedding,也是目前 RAG 的标配方案
业界改造 BERT 架构,搭配对比学习的训练方式,专门针对句子相似度、语义检索场景优化,主流模型包含 SBERT、SimCSE、BGE,还有 E5、Qwen3-Embedding 这类新生代模型。 1、SBERT(Sentence-BERT):采用双编码器结构,查询文本、知识库文档可以分开独立编码生成向量,文档向量能够提前离线计算存入向量库,用户查询时只需要实时生成查询向量,依靠余弦相似度匹配召回,速度直接提升好几个量级,牺牲了一点点精细匹配精度,换来检索效率的质变,完美适配 RAG 架构。 2、SimCSE:在对比学习层面做了优化,对同一句话施加两次不同的 dropout 当作正样本,同一个批次其他句子作为负样本拉开向量距离,修复了 BERT 原生向量分布集中、各向异性的问题,向量空间分布更加均匀,检索精度进一步提升,而且不需要人工标注样本,训练成本很低。 3、BGE 系列:国内中文 RAG 最常用的开源模型,基于对比学习在海量中英数据训练,同时配套了嵌入编码和重排 reranker 两套能力,bge-large-zh-v1.5 是中文知识库经典选型,bge-m3 还同时支持稠密、稀疏、多向量三种检索模式,适配中英混合文档。
除此之外第三代现在也衍生出不少进阶优化方向,都属于第三代框架内的升级:指令感知嵌入可以根据检索意图调整向量表征;Matryoshka 嵌套向量支持自由截断向量维度,平衡存储成本和检索精度;还有多模态 Embedding,能够同时编码文本、图片内容,实现图文混合检索。
整体总结 + 项目落地选择
第一代静态词向量、第二代原生 BERT 都不适合直接搭建 RAG 检索层,生产环境全部使用第三代句子级对比学习模型。我实际项目中,纯中文知识库本地部署 BGE 系列,中英混杂场景选用 bge-m3,同时一定会拿自身业务数据集跑 Hit@K 指标完成测评之后再正式上线,不会单纯依赖通用榜单选型。
小程序学习
WXML 模板语法
条件渲染
wx:if
在小程序中,使用 wx:if="{{condition}}" 来判断是否需要渲染该代码块:
<!-- pages/page/page.wxml -->
<view wx:if="{{condition}}"> true </view>
// pages/page/page.js
Page({
data: {
condition: true
}
})
也可以用 wx:elif 和 wx:else 来添加 else 判断:
<!-- pages/page/page.wxml -->
<view wx:if="{{type === 1}}"> 男 </view>
<view wx:elif="{{type === 2}}"> 女 </view>
<view wx:else> 保密 </view>
// pages/page/page.js
Page({
data: {
type: 2
}
})
结合 <block> 使用 wx:if
如果要一次性控制多个组件的展示与隐藏,可以使用一个 <block></block> 标签将多个组件包装起来,并在 <block> 标签上使用 wx:if 控制属性:
<!-- pages/page/page.wxml -->
<block wx:if="{{false}}">
<view> view1 </view>
</block>
<view> view2 </view>

注意:<block> 并不是一个组件,它只是一个包裹性质的容器,不会在页面中做任何渲染。
hidden
在小程序中,直接使用 hidden="{{ condition }}" 也能控制元素的显示与隐藏:
<!-- pages/page/page.wxml -->
<view hidden="{{condition}}"> view-hidden,条件为 true 隐藏,为 false 显示 </view>
// pages/page/page.js
Page({
data: {
condition: false
}
})
wx:if 与 hidden 的对比

列表渲染
wx:for
通过 wx:for 可以根据指定的数组,循环渲染重复的组件结构,语法示例如下:
<!-- pages/page/page.wxml -->
<view wx:for="{{array}}">
索引是:{{index}} 当前项是:{{item}}
</view>
// pages/page/page.js
Page({
data: {
array: [1,2,3]
}
})
默认情况下,当前循环项的索引用 index 表示;当前循环项用 item 表示:

手动指定索引和当前项的变量名
-
使用 wx:for-index 可以指定当前循环项的索引的变量名
-
使用 wx:for-item 可以指定当前项的变量名
<!-- 一看就知道是学生对象和学号 -->
<view wx:for="{{studentList}}" wx:for-item="student" wx:for-index="stuId">
名字:{{student.name}},学号:{{stuId}}
</view>
<!-- 看到 item 和 index,不知道具体是什么 -->
<view wx:for="{{studentList}}">
名字:{{item.name}},排名:{{index}}
</view>
wx:key 的使用
类似于 Vue 列表渲染中的 :key,小程序在实现列表渲染时,也建议为渲染出来的列表项指定唯一的 key 值, 从而提高渲染的效率:
<!-- pages/page/page.wxml -->
<view wx:for="{{userList}}" wx:key="id">{{item.name}}</view>
// pages/page/page.js
Page({
data: {
userList: [
{id: 1, name: 'red'},
{id: 2, name: 'yellow'},
{id: 3, name: 'white'}
]
}
})
WXSS 模板样式
什么是 WXSS
样式语言,用于美化 WXML 的组件样式,类似于网页开发中的 CSS
WXSS 和 CSS 的关系

rpx
什么是 rpx 尺寸单位
![]()
rpx 的实现原理

rpx 与 px 之间的单位换算

样式导入
什么是样式导入
使用 WXSS 提供的 @import 语法,可以导入外联的样式表
@import 的语法格式
@import 后跟需要导入的外联样式表的相对路径,用 ; 表示语句结束。示例如下:

这个 @import 规则的核心作用就是:把多个样式文件合并成一个来用,实现公共样式的复用和模块化管理。简单来说,就是写一次,到处使用,避免在多个文件里复制粘贴相同的 CSS 代码。
-
提取公共样式,避免重复(核心)
如果你的项目里有多个页面都需要让文字内边距为 5px(即 .small-p 这个类),你不用在每个页面的 .wxss 文件里都写一遍 padding:5px;。
-
你只需要在
common.wxss里定义一次。 -
然后在需要的页面(或全局
app.wxss)中通过@import引入。 -
之后,当前文件所在的页面就能直接使用
.small-p这个类名了。
-
统一维护,改一处即可
你截图里是在 app.wxss(全局样式表)中导入的。这意味着 common.wxss 里的样式会变成全局样式,作用于所有页面。
-
如果未来设计师要求把
5px改成10px,你只需要修改common.wxss这一个文件,所有引入过它的页面都会自动生效。 -
如果不使用
@import,你就得挨个去修改几十个页面的样式文件,极易漏改。
-
按功能模块拆分文件(工程化)
随着项目变大,你可以把样式按功能拆开:
-
common.wxss(通用工具类,如边距、字体) -
theme.wxss(主题颜色变量) -
components.wxss(组件样式)
然后通过 @import 在 app.wxss 里把它们像拼图一样组合起来,这样团队协作时不容易产生代码冲突。
全局样式和局部样式


全局配置
全局配置文件及常用的配置项

window
小程序窗口的组成部分

了解 window 节点常用的配置项

设置导航栏的标题
设置步骤:app.json -> window -> navigationBarTitleText

设置导航栏的背景色
设置步骤:app.json -> window -> navigationBarBackgroundColor
跟上面一样的位置!

设置导航栏的标题颜色
设置步骤:app.json -> window -> navigationBarTextStyle
注意: navigationBarTextStyle 的可选值只有 black 和 white

全局开启下拉刷新功能
概念:下拉刷新是移动端的专有名词,指的是通过手指在屏幕上的下拉滑动操作,从而重新加载页面数据的行为。
设置步骤:app.json -> window -> 把 enablePullDownRefresh 的值设置为 true
注意:在 app.json 中启用下拉刷新功能,会作用于每个小程序页面!
设置下拉刷新时窗口的背景色
当全局开启下拉刷新功能之后,默认的窗口背景为白色。如果自定义下拉刷新窗口背景色,设置步骤为: app.json -> window -> 为 backgroundColor 指定16进制的颜色值 #efefef。

置下拉刷新时 loading 的样式
当全局开启下拉刷新功能之后,默认窗口的 loading 样式为白色,如果要更改 loading 样式的效果,设置步骤为 app.json -> window -> 为 backgroundTextStyle 指定 dark 值。
注意: backgroundTextStyle 的可选值只有 light 和 dark。

设置上拉触底的距离
概念:上拉触底是移动端的专有名词,通过手指在屏幕上的上拉滑动操作,从而加载更多数据的行为。设置步骤: app.json -> window -> 为 onReachBottomDistance 设置新的数值
注意:默认距离为50px,如果没有特殊需求,建议使用默认值即可。
tabBar
什么是 tabBar
tabBar 是移动端应用常见的页面效果,用于实现多页面 的快速切换。小程序中通常将其分为:底部 tabBar与顶部 tabBar。
注意:tabBar中只能配置最少 2 个、最多 5 个 tab 页签;当渲染顶部 tabBar 时,不显示 icon,只显示文本。

tabBar 的 6 个组成部分

tabBar 节点的配置项

每个 tab 项的配置选项

实践



页面配置
页面配置文件的作用
小程序中,每个页面都有自己的 .json 配置文件,用来对当前页面的窗口外观、页面效果等进行配置。
页面配置和全局配置的关系
小程序中,app.json 中的 window 节点,可以全局配置小程序中每个页面的窗口表现。
如果某些小程序页面想要拥有特殊的窗口表现,此时,“页面级别的 .json 配置文件”就可以实现这种需求。
注意:当页面配置与全局配置冲突时,根据就近原则,最终的效果以页面配置为准。
面配置中常用的配置项

网络数据请求
小程序中网络数据请求的限制

配置 request 合法域名





// pages/page/page.js
Page({
// 页面数据(可选)
data: {
swiperList: [],
gridList: []
},
// 生命周期:页面加载时自动执行
onLoad: function (options) {
console.log('页面加载了,开始请求数据...');
// 调用两个请求函数
this.getSwiperList(); // GET 请求
this.getGridList(); // POST 请求
},
// 1. GET 请求示例:获取轮播图数据
getSwiperList() {
wx.request({
url: 'https://www.eslook.cn/api/get', // 替换成你的真实接口
method: 'GET',
data: {
name: 'zs',
age: 22
},
success: (res) => {
console.log('GET 请求成功,返回的数据:', res);
// 如果需要把数据存到页面 data 中,可以这样:
// this.setData({ swiperList: res.data });
},
fail: (err) => {
console.error('GET 请求失败:', err);
}
});
},
// 2. POST 请求示例:获取九宫格数据
getGridList() {
wx.request({
url: 'https://www.eslook.cn/api/post', // 替换成你的真实接口
method: 'POST',
data: {
name: 'ls',
gender: '男'
},
success: (res) => {
console.log('POST 请求成功,返回的数据:', res);
// 如果需要存储数据:
// this.setData({ gridList: res.data });
},
fail: (err) => {
console.error('POST 请求失败:', err);
}
});
}
});
关于跨域和 Ajax 的说明

更多推荐



所有评论(0)