RAG(检索增强生成)

大语言模型(LLM)能流畅地写文章、回答问题、编写代码,但它有一个根本性的局限:它的知识被冻结在训练数据截止的那一刻。问它昨天发生了什么新闻?不知道。问它你公司内部的技术文档?更不知道。 更棘手的是,LLM有时候会"一本正经地胡说八道"——这就是所谓的**幻觉(Hallucination)**问题。模型会自信满满地编造一个不存在的论文引用、一个错误的API参数、甚至一个虚构的历史事件。 **RAG(Retrieval-Augmented Generation,检索增强生成)**正是为解决这些问题而生。它的核心思想简单而有效:在让LLM回答之前,先从外部知识库中检索相关信息,把这些信息塞到提示词(Prompt)里,让LLM基于真实资料来回答。 一、RAG的核心思想 1.1 一个直觉的类比 想象你是一个闭卷考试的学生(这就是纯LLM)——只能靠记忆作答,记不清的地方就只能猜。 现在允许你带一本参考书(这就是RAG)——遇到不确定的问题,先翻书找到相关段落,然后基于书中的内容来组织答案。 显然,开卷考试的准确率会高得多。 1.2 RAG的核心价值 LLM的局限 RAG的解决方案 知识过期,无法获取训练截止后的信息 随时更新知识库,实时生效 幻觉问题,生成不存在的信息 基于真实文档生成,可追溯来源 垂直领域知识不足 接入专业知识库,提供领域专业回答 更新成本高,Fine-tuning代价大 只需更新知识库,成本低且灵活 1.3 RAG的基本流程 graph LR Q["用户提问"] --> R["检索器Retriever"] KB["知识库"] --> R R --> C["相关文档片段"] C --> P["构造Prompt问题 + 相关文档"] P --> LLM["大语言模型"] LLM --> A["生成回答"] 三个核心步骤: 检索(Retrieve):根据用户的问题,从知识库中找到最相关的文档片段 增强(Augment):将检索到的信息与原始问题一起构造成Prompt 生成(Generate):LLM基于增强后的Prompt生成回答 二、从关键词到语义:检索方式的演进 2.1 传统关键词搜索的局限 最简单的检索方式是关键词匹配。比如用户问"如何购买商品?",搜索引擎会提取关键词"购买"、“商品”,然后去匹配包含这些词的文档。 问题很明显: 文档中写的是"下单流程说明"——语义完全一致,但没有任何关键词重合 无法理解同义词:“购买"vs"下单"vs"采购"vs"买” 必须精确匹配关键词,用词不同就检索不到 我们需要一种理解语义的检索方式。 2.2 文本嵌入(Text Embedding) 文本嵌入模型(如OpenAI的text-embedding-3-small、BGE等)将文本转换为高维向量(通常512/768/1024/1536维),使得语义相近的文本在向量空间中距离相近: "如何购买商品?" → [0.23, -0.15, 0.82, ...] (768维向量) "下单流程说明" → [0.21, -0.12, 0.79, ...] ← 向量很近,语义匹配! "今天天气真好" → [-0.65, 0.43, -0.21, ...] ← 向量很远 在高维向量空间中,每个维度代表不同的语义特征(如情感倾向、主题领域、动作类型等)。语义相似的文本会在空间中聚集在一起——“购买”、“下单”、“采购"形成一个语义簇,而"天气预报"则远离这个簇。 ...

May 2, 2026

Transformer架构原理

ChatGPT、Claude、Gemini——这些模型的核心都是同一个架构:Transformer。它在2017年由Google的论文"Attention Is All You Need"提出,彻底改变了自然语言处理(NLP)领域,随后席卷了计算机视觉、语音、蛋白质折叠等几乎所有AI领域。 我们从最直觉的角度出发,一步步搞清楚Transformer到底在做什么。 一、从序列到序列:Transformer要解决什么问题 1.1 语言的本质是序列 一句话是一个词的序列: "我 今天 很 开心" → [我, 今天, 很, 开心] 语言模型的核心任务是:给定前面的词,预测下一个词。 "我 今天 很" → ?(开心?难过?忙碌?) 但词与词之间的关系非常复杂。“它"指代谁?“银行"是河岸还是金融机构?这取决于上下文中其他词的信息。 1.2 RNN的困境 在Transformer之前,处理序列的主流方法是循环神经网络(RNN)。RNN像一个人从左到右逐词阅读,用一个"记忆状态"来记住看过的内容: graph LR X1["我"] --> H1["h₁"] H1 --> H2["h₂"] X2["今天"] --> H2 H2 --> H3["h₃"] X3["很"] --> H3 H3 --> H4["h₄"] X4["开心"] --> H4 问题在于: 长距离依赖困难:信息需要经过很多步才能从序列开头传到末尾,容易丢失 无法并行:必须逐步处理,前一步完成才能开始后一步,训练很慢 Transformer的核心洞察是:不需要逐步处理,让序列中的每个位置直接与所有其他位置交互。这就是"注意力(Attention)“机制。 二、词嵌入:给词语一个数学身份 2.1 从独热编码到词向量 计算机不认识"猫”、“狗"这样的文字。我们需要把词转换成数字。 最朴素的方式是独热编码(One-Hot Encoding):假设词汇表有50,000个词,每个词用一个50,000维的向量表示,只有对应位置是1,其余全是0。 "猫" → [0, 0, ..., 1, ..., 0, 0] (第3721个位置是1) "狗" → [0, 0, ..., 0, 1, ..., 0, 0] (第5042个位置是1) 问题:在这种表示下,“猫"和"狗"的距离与"猫"和"经济学"的距离一样远——完全丢失了语义信息。 ...

May 2, 2026

iOS中AI Chat实战

做一个能跑的AI聊天Demo只需要几十行代码:拼一个HTTP请求、把模型返回的文本显示出来即可。但要做一个达到生产级体验的AI Chat,就会面对一堆并不"AI"的工程问题:字怎么一个个"蹦"出来?Markdown怎么边流边渲染?模型想让前端弹一个卡片甚至一个表单,该怎么办?用户点击卡片里的按钮怎么回传给模型? 本文以iOS为实现目标,系统梳理AI Chat的工程架构,重点讲清楚两件事:流式输出与A2UI(Agent to UI),同时覆盖工具调用、取消、多模态、端侧推理等常见高级能力。 一、整体架构 一个完整的iOS AI Chat客户端,通常可以拆成如下几层: graph TB subgraph UI ["展示层"] LIST["消息列表UICollectionView / List"] CELL["消息Cell文本 / Markdown / A2UI Surface"] INPUT["输入区文本 / 图片 / 语音"] end subgraph VM ["ViewModel层"] STATE["会话状态机"] STREAM["流式拼装器"] TOOL["工具执行器"] end subgraph NET ["网络层"] SSE["SSE / HTTP 分块"] PARSER["协议解析OpenAI / Claude / A2UI"] end subgraph DATA ["数据层"] DB["消息持久化GRDB / WCDB / CoreData"] CTX["上下文裁剪Token预算"] end INPUT --> STATE STATE --> NET NET --> PARSER PARSER --> STREAM STREAM --> CELL STREAM --> TOOL TOOL --> NET STATE --> DB DB --> CTX CTX --> NET 几个关键特征: ...

May 2, 2026

反向传播算法

在上一篇文章中,我们构建了一个神经网络,了解了前向传播是如何产生预测的,也知道了损失函数可以衡量预测有多差。但我们遗留了最核心的问题:那13,002个参数到底该怎么调整,才能让网络变得更好? 这就是反向传播算法(Backpropagation)要回答的问题。它是神经网络学习的核心引擎,几乎所有现代深度学习的训练都建立在它之上。 一、梯度下降:从直觉开始 1.1 一个简单的类比 想象你站在一片浓雾中的山地上,看不到全貌,但你能感受到脚下地面的倾斜方向。你的目标是找到最低的山谷。最自然的策略是什么? 顺着脚下最陡的下坡方向走一小步。 这就是**梯度下降(Gradient Descent)**的核心思想。 1.2 数学表述 假设损失函数 $L$ 取决于所有参数 $\theta = (w_1, w_2, ..., w_n, b_1, b_2, ..., b_m)$。 梯度是一个向量,指向损失增长最快的方向: $$\nabla L = \left(\frac{\partial L}{\partial w_1}, \frac{\partial L}{\partial w_2}, \cdots, \frac{\partial L}{\partial b_m}\right)$$梯度的每个分量告诉我们:如果微微增大某个参数,损失会增加多少。 要降低损失,我们朝梯度的反方向迈步: $$\theta_{\text{new}} = \theta_{\text{old}} - \eta \cdot \nabla L$$其中 $\eta$ 是学习率(learning rate),控制每一步的步长。 1.3 学习率的微妙之处 学习率 下降行为 结果 太小 每步挪动极小,到达局部最小值后就停住了,无法跳出 收敛极慢,可能困在局部最小值 太大 一步跨过了最低点,又跨回来,反复来回 在最低点附近震荡,甚至发散 合适 每步大小适中,沿着曲面稳步走向谷底 稳步下降,逐渐逼近最优解 1.4 梯度下降的关键问题 现在整个问题归结为一件事:怎么高效地计算 $\nabla L$,即损失对每一个参数的偏导数? 你可能会想:对每个参数微微扰动一下,看看损失变化了多少。但如果有13,002个参数,每个参数都扰动一次,就需要做13,002次前向传播——太慢了。 ...

May 2, 2026

神经网络原理

想象你手写了一个数字"3",然后拿给计算机看——它是怎么"认出"这是3而不是8的?这个看似简单的问题,背后隐藏着整个深度学习的核心思想。让我们从最直觉的方式开始,一层一层揭开神经网络的面纱。 一、从像素到数字:问题的本质 1.1 手写数字识别 假设我们有一张28x28像素的灰度手写数字图片。每个像素是一个0到1之间的数字——0代表全黑,1代表全白。这意味着一张图片其实就是一个包含784个数字的列表。 图片 → [0.0, 0.0, 0.12, 0.85, 0.93, ..., 0.0] (784个数字) 我们的目标是设计一个函数,输入这784个数字,输出10个数字,分别代表这张图是0~9每个数字的概率: f(784个像素值) → [P(0), P(1), P(2), ..., P(9)] 比如输入一张手写的"3",我们期望输出类似: [0.01, 0.02, 0.05, 0.89, 0.01, 0.01, 0.00, 0.01, 0.00, 0.00] ^^^^ "3"的概率最高 这就是神经网络要解决的问题。但关键问题是:这个函数 f 长什么样? 1.2 为什么传统编程行不通 你可能会想:能不能写一堆if-else规则?比如"如果上方有一个圆弧,下方也有一个圆弧,那就是3"。 问题在于:人类写字的方式千变万化。同一个"3",有人写得圆润,有人写得尖锐,有人偏左,有人偏右。你几乎不可能用明确的规则覆盖所有情况。 我们需要的是一种从数据中自动学习规则的方法——这就是神经网络。 二、神经网络的结构 2.1 神经元:最基本的单元 让我们从最简单的单元开始。一个神经元本质上就是一个持有数字的容器。这个数字叫做激活值(activation),取值范围通常在0到1之间。 你可以把它想象成一个灯泡:0表示完全熄灭,1表示完全点亮,中间的值表示不同亮度。 2.2 层:神经元的组织方式 神经网络把神经元组织成层(layer): graph LR subgraph 输入层 [输入层 - 784个神经元] I1[像素1] I2[像素2] I3[...] I784[像素784] end subgraph 隐藏层1 [隐藏层1 - 16个神经元] H1_1[h1] H1_2[h2] H1_3[...] H1_16[h16] end subgraph 隐藏层2 [隐藏层2 - 16个神经元] H2_1[h1] H2_2[h2] H2_3[...] H2_16[h16] end subgraph 输出层 [输出层 - 10个神经元] O0[0] O1[1] O2[...] O9[9] end I1 --> H1_1 I1 --> H1_2 I784 --> H1_16 H1_1 --> H2_1 H1_16 --> H2_16 H2_1 --> O0 H2_16 --> O9 输入层:784个神经元,每个对应一个像素值 隐藏层:中间的层(这里用2层,每层16个神经元) 输出层:10个神经元,对应数字0~9 为什么叫"隐藏层"?因为在训练数据中,我们只知道输入(像素)和输出(标签),中间层在"学什么"我们并不直接指定——它们是被自动学出来的。 ...

May 2, 2026

OpenClaw 源码导读(三):Agent Harness — 为"每一家 LLM 都能兜住"而生的执行管线

系列第一篇把 OpenClaw 的架构地图摊开,第二篇深入 Gateway 控制平面。这一篇,我们钻进整个项目的心脏——src/agents/,看 OpenClaw 怎么处理最复杂的一件事:当 Gateway 接到一条用户消息之后,怎么把它变成一轮真正的 LLM turn + Tool 循环。 这一层代码的体量和复杂度非常惊人: src/agents/pi-embedded-runner/run.ts 单文件 2160 行,是整个 agent 循环的驱动器。 src/agents/pi-embedded-runner/compact.ts 1148 行,专门处理上下文压缩。 整个 src/agents/ 目录有 830+ 个文件,光 anthropic-*.ts 就有近 20 个(为了 Messages API 的各种 edge case)。 同时支持 Anthropic、OpenAI、Google Gemini、Bedrock、Vertex、OpenRouter、Z.AI/GLM、MiniMax、Qwen、Ollama、Kilocode 等十多个 provider。 但这些"量"的背后,是一个只有一个接口的核心抽象:AgentHarness。本文就从这个抽象出发,一路看到 2160 行循环如何处置真实世界里十几类失败。 一、Harness 抽象:只有一个 runAttempt 1. 为什么不用 LangChain 风格的"Agent Framework" 读过 LangChain 或 CrewAI 的人第一眼会期望看到 AgentExecutor、AgentChain、Memory、Callback、Tool 这些类。OpenClaw 里一个都没有。 它的选择是:把整个 agent 循环当成一个黑盒,对外只暴露一个接口: export type AgentHarness = { id: string; label: string; pluginId?: string; supports(ctx: AgentHarnessSupportContext): AgentHarnessSupport; runAttempt(params: AgentHarnessAttemptParams): Promise<AgentHarnessAttemptResult>; compact?(params: AgentHarnessCompactParams): Promise<AgentHarnessCompactResult | undefined>; reset?(params: AgentHarnessResetParams): Promise<void> | void; dispose?(): Promise<void> | void; }; 五个方法,其中三个可选: ...

May 2, 2026

OpenClaw 源码导读(四):Channels、Nodes 与扩展生态

前三篇我们看清了 OpenClaw 的三副骨架:架构全景、Gateway 控制平面、Agent Harness 执行管线。这一篇是压轴篇,我们看它的"血肉"——channels/、node-host/、canvas-host/、plugins/、skills/、sandbox/。OpenClaw 真正"好用"的感觉,是从这一层来的:一条消息从 WhatsApp 进来、触发 iOS 上的摄像头、在 Android 上渲染一张 Canvas、最后把结果发回 Slack 线程——整个过程中 Gateway 本身一行代码都不需要改。 本文涵盖 118 个 channel/provider extension、53 个 skill、iOS/macOS/Android node 客户端、A2UI Canvas 协议、以及 Docker sandbox 的整套隔离模型。 一、Channels:118 个 extension 是怎么"插"进来的 1. ChannelPlugin 接口 OpenClaw 所有 channel(微信、Telegram、Slack、iMessage、IRC、Discord……)本质上都是实现同一个接口 ChannelPlugin: export type ChannelPlugin<ResolvedAccount = any, Probe = unknown, Audit = unknown> = { id: ChannelId; meta: ChannelMeta; capabilities: ChannelCapabilities; defaults?: { queue?: { debounceMs?: number; }; }; reload?: { configPrefixes: string[]; noopPrefixes?: string[] }; setupWizard?: ChannelPluginSetupWizard; config: ChannelConfigAdapter<ResolvedAccount>; configSchema?: ChannelConfigSchema; setup?: ChannelSetupAdapter; pairing?: ChannelPairingAdapter; security?: ChannelSecurityAdapter<ResolvedAccount>; groups?: ChannelGroupAdapter; mentions?: ChannelMentionAdapter; outbound?: ChannelOutboundAdapter; status?: ChannelStatusAdapter<ResolvedAccount, Probe, Audit>; gatewayMethods?: string[]; gateway?: ChannelGatewayAdapter<ResolvedAccount>; auth?: ChannelAuthAdapter; approvalCapability?: ChannelApprovalCapability; elevated?: ChannelElevatedAdapter; commands?: ChannelCommandAdapter; lifecycle?: ChannelLifecycleAdapter; secrets?: ChannelSecretsAdapter; allowlist?: ChannelAllowlistAdapter; doctor?: ChannelDoctorAdapter; bindings?: ChannelConfiguredBindingProvider; conversationBindings?: ChannelConversationBindingSupport; streaming?: ChannelStreamingAdapter; threading?: ChannelThreadingAdapter; messaging?: ChannelMessagingAdapter; agentPrompt?: ChannelAgentPromptAdapter; directory?: ChannelDirectoryAdapter; resolver?: ChannelResolverAdapter; actions?: ChannelMessageActionAdapter; heartbeat?: ChannelHeartbeatAdapter; agentTools?: ChannelAgentToolFactory | ChannelAgentTool[]; }; 这个接口一共有 40 多个可选的 adapter,每个对应一种能力: ...

May 2, 2026

OpenClaw 源码导读(二):Gateway 控制平面 — 一条 WebSocket 连上所有人

在系列第一篇里我们看到,OpenClaw 的核心隐喻是 “Gateway 是控制平面”。它不存转储消息、不排队、不持久化事件流,而是一个长在 127.0.0.1:18789 上的状态化 RPC 服务器,所有客户端(CLI、macOS.app、iOS/Android Node、WebChat 浏览器、Canvas iframe)都靠一条 WebSocket 接入。 这一篇拆开控制平面的五个层面: 进程入口:CLI 怎么从 openclaw gateway 走到 Node 进程常驻 WebSocket 协议:握手帧、RPC 帧、Event 帧、状态快照与版本号 鉴权矩阵:Token / Password / Device Token / Bootstrap Token / Tailscale Whois / Trusted Proxy Pairing:陌生客户端怎么合法加入(setup code、pairing store、allowFrom) Session 模型:Session Key 组成规则、多 Agent 路由、Session ID 模糊匹配 注:下文所有 src/ 路径都指 openclaw/openclaw 仓库根下。 一、CLI 到 Gateway 的六段路径 第一篇概览过 src/entry.ts 和 src/cli/run-main.ts,这里按调用序重新画一遍,把每个关键函数在 graph 上标出来: flowchart TD A0["openclaw gateway --port 18789"] --> A1 A1["entry.tsmain guard + process.title"] --> A2 A2["normalizeEnv + enableCompileCache()"] --> A3 A3["ensureCliRespawnReady()按需 spawn 子 Node"] --> A4 A4["parseCliContainerArgsparseCliProfileArgs"] --> A5 A5["tryHandleRootVersionFastPathtryHandleRootHelpFastPath"] -->|miss| A6 A6["runMainOrRootHelp → runCli(argv)"] --> B0 B0["run-main.tscontainer? container in progress"] --> B1 B1["loadCliDotEnv (~/.openclaw/.env)"] --> B2 B2["assertSupportedRuntime (Node≥22.16)"] --> B3 B3["tryRouteCli(fast-path 子命令路由)"] -->|miss| B4 B4["buildProgram() + commander 注册"] --> B5 B5["installUnhandledRejectionHandler"] --> B6 B6["registerPluginCliCommandsFromValidatedConfig (lazy)"] --> B7 B7["program.parseAsync"] --> C0 C0["gateway 命令匹配 → gatewayCommandAction"] --> C1 C1["lock 文件 + 端口 acquire"] --> C2 C2["HTTP/WS server 启动"] --> C3 C3["channels/plugins/cron/canvas-host 启动"] --> C4["Ready"] 1. 为什么要自己 spawn 自己 ensureCliRespawnReady() 是 src/entry.ts 里最容易被忽视但非常重要的段落: ...

May 2, 2026

OpenClaw 源码导读(一):架构总览 — 为"单个主人"而设计的 AI Gateway

2025 年 11 月,Peter Steinberger(PSPDFKit 创始人、前著名 iOS 社区人物)把自己折腾出来的个人 AI 助手 Molty 开源为 OpenClaw。短短几个月,仓库吃到 35 万+ star、93 个 release、360 多个 contributor,核心代码量在 TypeScript/Swift/Kotlin/Go/Python 之间横跨 40 多万行。它和 Claude Code、Codex CLI 乍看都是"跑在本地的 CLI Agent",但设计哲学完全不同——Claude Code 是一个编程助手,OpenClaw 是一个长在你设备上的私人秘书:它挂在 WhatsApp/Telegram/iMessage/微信 的 IM 客户端后面,能自己发消息、自己开浏览器、自己调用 iOS/Android 上的摄像头和麦克风。 本文是 OpenClaw 源码导读系列的第一篇,目标是把整个项目的"地图"摊开。先讲清楚它要解决什么问题、在怎样的信任模型下运行,再把仓库 100 多个顶层模块拎出来分类,最后给出后续系列文章的导航。 一、它到底是什么 1. 一句话定义 OpenClaw 官方给自己的定位是 “Personal AI Assistant. Any OS. Any Platform. The lobster way.” 翻译过来就是:一个在你自己设备上运行、从你自己现有的聊天工具里和你对话的个人 AI 助手。 这个定位里藏着三个关键差异: Personal:它不是多租户 SaaS,也不是团队协作工具,而是单一主人的私人助手。整个信任模型就是为"只有一个 operator"优化的。 Any OS / Any Platform:Gateway 是 Node 进程,可以跑在 macOS/Linux/Windows(WSL2)、甚至 Fly.io/Docker/NAS 上;客户端包含 iOS/Android/macOS 原生 App,还有 Web Dashboard。 The lobster way:作者把它拟人化成一只太空龙虾 Molty,这是一个品牌/吉祥物层面的设计,但也反映了项目的"玩心"。 2. 和 Claude Code 对比:两个相似却完全不同的 CLI Agent 由于作者 Steipete 本身是 Claude Code 的重度用户和 Anthropic 的合作者,社区最常问的问题就是"和 Claude Code 有啥区别"。这里先画一张对比表: ...

May 2, 2026