LLM 基础速通
副标题:Token、上下文、温度、幻觉与 RAG(前端够用版)
目标:用前端能理解的方式,把“够用的基础”讲清楚——你要能解释、能排障、能做产品决策。
目录
- LLM 是什么:从“预测下一个词”到“产品能力”
- Token 与上下文窗口:为什么越聊越贵、越聊越跑偏
- 温度(Temperature)与 Top-p:如何控制“稳定 vs 发散”
- 幻觉:为什么会胡说,以及怎么降低
- Function Calling / Tools:让模型做“可控的事”
- RAG:文档问答/企业知识库的关键能力
- 前端工程师的“够用速记卡”
- 附:你可以直接抄走的“默认参数组合”
- 最小可运行 Demo(LLM 基础版)
- 完整可运行代码(Token/上下文估算)
LLM 是什么:从“预测下一个词”到“产品能力”
把大语言模型理解成一个“基于上下文,预测下一段文本”的系统就够用了。对你做应用开发来说,更重要的是:
- 能力边界:擅长总结、改写、生成、分类、对话、工具编排;不擅长保证事实正确
- 输出不确定:同样输入可能得到不同输出(可用参数控制)
- 对上下文敏感:你给它什么,它就按什么“演”
因此应用层的工作,本质是:把不确定的能力,变成可控的产品体验。
Token 与上下文窗口:为什么越聊越贵、越聊越跑偏
Token 是什么
你可以把 Token 粗略理解为“文本片段的计费单位”。中文通常 1 个汉字 ≠ 1 个 Token,但你不需要精确换算,关键是知道:
- 输入 Token:system + user + 历史对话 + 检索到的文档片段
- 输出 Token:模型生成的回答
- 成本 = 输入 + 输出(通常按 Token 计费)
一个可落地的 Token 预算算例(前端视角)
假设你做一个“文档问答”页面,想控制每次对话的成本上限。你可以把一次请求的 Token 拆成 4 块:
- system:规则与角色(例如 200 tokens)
- history:历史对话(例如 1500 tokens)
- rag_context:检索证据(例如 3 段 * 350 = 1050 tokens)
- user:用户问题(例如 80 tokens)
那么输入大约是:(200 + 1500 + 1050 + 80 = 2830) tokens
如果你把 max_tokens 设置为 600,那么单次请求的总量大约是:(2830 + 600 = 3430) tokens。
你可以据此在产品里做两件事:
- 成本可控:把
max_tokens、RAG 段数、history 长度变成硬限制 - 体验可解释:当超预算时提示“已自动压缩对话历史/减少引用段落”
实操建议:先定“每次回答允许的最大证据段数(TopN)”和“max_tokens”,再决定历史对话保留策略。
上下文窗口(Context Window)
上下文窗口是模型一次能“看到”的最大 Token 数。超出部分会被截断或需要你自己压缩/摘要。
产品层常见现象:
- 越聊越贵:历史对话越来越长
- 越聊越容易跑偏:历史里混入了错误/无关信息,模型被带偏
- 长文档问答不准:把整本文档塞进上下文不现实
应用层对策(你会在后续章节用到):
- 对话摘要:把历史压缩成结构化记忆(要点/偏好/约束)
- 检索替代堆上下文:用 RAG 只塞相关片段
- 限制输入输出:UI 侧限制字数、服务端限制 max_tokens
示例:对话摘要(Memory)怎么写才“能用”
很多项目的摘要失败,是因为把历史“概括成一段话”,结果信息丢失或不可控。建议用结构化摘要,并固定字段:
你是对话摘要器。请把以下对话压缩成“可用于后续对话”的结构化记忆。
输出为 JSON,字段必须包含:
- user_goal: 用户目标(1 句话)
- constraints: 约束(数组,最多 5 条)
- decisions: 已确认的关键决策(数组,最多 5 条)
- open_questions: 未解决问题(数组,最多 5 条)
- glossary: 专有名词解释(对象,可选)
规则:
- 不要编造;缺失就留空数组
- 严禁输出多余字段
前端怎么用这份摘要:
- UI 状态 → constraints/decisions:让“已选项/已确认”进入上下文
- 继续追问 → open_questions:直接用未解问题驱动下一轮追问
常见坑:上下文不是越长越好
- 把整篇文档塞进去:成本暴涨,还会稀释关键信息,准确率反而下降
- 把无关历史也带上:会把模型带偏(尤其在多任务对话里)
- 没有硬限制:上线后很容易被“长对话用户”拖垮成本
温度(Temperature)与 Top-p:如何控制“稳定 vs 发散”
你可以把它理解成“随机性旋钮”:
- 温度低:更稳定、更像“按规则办事”(适合问答、抽取、格式化)
- 温度高:更发散、更有创意(适合文案、头脑风暴)
- Top-p:另一种控制采样范围的方式(实际使用中常与温度二选一或搭配)
应用建议(经验型):
- 结构化输出/需要稳定:温度低(或 Top-p 小一些)
- 创意生成:温度稍高
- 多步骤任务:把“生成”与“校验/格式化”拆成两次调用(一次负责想,另一次负责收敛)
示例:同一个任务,不同温度的差异
任务:把一段中文需求改写成 3 条验收标准。
- 温度低(更稳定):更像“按模板填空”,适合结构化、抽取、总结
- 温度高(更发散):会补充更多“想象的细节”,适合脑暴,但更容易跑题/编造
推荐默认值(可以当产品默认参数):
- 问答/总结/抽取:
temperature=0.2~0.4 - 写作/文案/创意:
temperature=0.7~1.0 - Top-p:如果使用
top_p,通常就把temperature固定较低(或反过来二选一),避免双重随机导致不可控
前端产品化建议:把参数变成“用户可理解的开关”
- “更严谨/更有创意”滑杆 → 映射到 temperature
- “回答更短/更详细” → 映射到 max_tokens + 章节要求(而不仅仅是 max_tokens)
幻觉:为什么会胡说,以及怎么降低
为什么会幻觉
模型的目标是“生成看起来合理的文本”,不是“保证事实正确”。当它缺信息时,可能会:
- 编造引用、编造数字、编造链接
- 把旧知识当新知识
- 把用户的暗示当事实
怎么降低(应用层可控手段)
从易到难给一组可落地的手段:
- 明确约束:不知道就说不知道;禁止编造引用/链接
- 要求引用证据:回答必须给出来源片段(RAG)
- 工具替代猜测:需要实时信息就调用工具/接口,不让它猜
- 分步推理外显/内隐:让模型先列检查清单再输出(或用“自检”二次调用)
- 输出校验:JSON schema 校验、字段缺失补问、格式不对重试
可直接复用的“拒答 + 引用”模板(推荐用于 RAG)
你是严谨的助手。你只能基于【证据】回答。
规则:
1) 如果证据不足以支持结论:请明确回答“根据当前证据无法确定”,并列出需要补充的信息。
2) 严禁编造链接、编号、数据、来源。
3) 每个关键结论后必须标注引用,如 [S1] [S2]。
输出格式:
- 结论(带引用)
- 依据(引用列表)
- 不确定点与需要补充的问题
示例:二次“自检”降低幻觉(便宜但有效)
第一次生成后,再用一个更短的检查 Prompt 做校验:
请检查下面回答是否存在“无证据推断/编造引用/数字不一致/与证据矛盾”。
若存在,请输出:问题列表 + 修正后的回答(仍需引用)。
实战经验:这类“自检”对引用完整性、格式稳定性提升明显,成本通常远低于一次完整重写。
Function Calling / Tools:让模型做“可控的事”
当你需要模型“做事”而不是“聊天”,最重要的思想是:
- 模型负责决策/抽取(要调用哪个工具、参数是什么)
- 系统负责执行(真正发请求、查库、算价格、读文件)
常见工具场景:
- 查询订单/权限判断
- 生成 PRD 的结构化字段(而不是一大段散文)
- 文档检索(先检索,再回答)
工程要点:
- 工具参数要可验证:字段类型、必填项、长度限制
- 工具输出要可追溯:日志里记录 tool 输入输出(脱敏)
- 避免越权:服务端再次做权限校验,不信任模型
示例:用 Tools 把“猜”变成“查”(端到端思路)
场景:用户问“我的订单 12345 现在是什么状态?”
如果让模型直接回答,它只能猜;正确做法是让模型调用工具查订单。
1)工具定义(JSON Schema 思路)
{
"name": "getOrderStatus",
"description": "查询订单状态",
"parameters": {
"type": "object",
"properties": {
"orderId": { "type": "string", "description": "订单号" }
},
"required": ["orderId"]
}
}
2)服务端执行(关键是:不信任模型、二次鉴权)
// 伪代码:服务端收到模型的 tool 调用请求后
async function handleGetOrderStatus(userId: string, orderId: string) {
// 1) 鉴权:这个 user 是否有权限查这个订单
await assertOrderPermission(userId, orderId);
// 2) 真查:从数据库/内部 API 查询
return await ordersService.getStatus(orderId);
}
3)把工具结果回注给模型生成“可解释回答”
让模型基于工具结果输出,同时要求“不确定就说不确定”,避免它补细节。
前端加分点:把工具执行过程做成“可展开的步骤”(类似调试面板),用户信任会显著提升。
RAG:文档问答/企业知识库的关键能力
RAG(Retrieval-Augmented Generation)可以理解为:
先“检索相关资料”,再“基于资料回答”,并尽量“引用来源”。
它解决的是:把“我不知道”变成“我去文档里查一下再回答”。
典型流程(你后面会实战):
- 文档切分(chunk)
- 每个 chunk 做 Embedding(向量表示)
- 用户提问也做 Embedding
- 向量检索取 TopK 相关 chunk
- 把 chunk 作为“证据”塞进 Prompt,让模型回答并引用
关键点(很多项目翻车就翻在这里):
- 切分策略:太大检索不准,太小上下文不够
- 引用输出:让用户能点开“证据来源”
- 评估与迭代:用固定问题集回归测试命中率/引用率
图示:RAG 的最小可行 Pipeline(Mermaid)
示例:证据输入格式(让引用变得“可实现”)
把检索到的 chunk 组织成“可引用编号”,并带元数据:
【证据】
[S1] doc=《用户手册》 page=12 title=安装
内容:...(chunk 文本)
[S2] doc=《FAQ》 section=退款
内容:...(chunk 文本)
然后在规则里要求:
- 每个关键结论后必须写 [S1]/[S2]
- 没证据就拒答或提出需要补充的问题
最小评估表(作品集可直接用)
- 检索命中率:正确证据是否出现在 TopK
- 引用准确率:引用是否真的支撑结论
- 失败类型:没命中 / 命中但噪音大 / 引用缺失 / 生成跑题
前端工程师的“够用速记卡”
- Token/上下文
- 对话越长越贵、越容易跑偏:用摘要 + RAG + 限制
- 温度/Top-p
- 低:稳定;高:发散;结构化输出优先低
- 幻觉
- 靠约束 + 引用 + 工具 + 校验降低
- Tools
- 模型决定、系统执行;权限/校验在服务端做二次防线
- RAG
- 企业知识库/文档问答核心;切分、检索、引用、评估缺一不可
附:你可以直接抄走的“默认参数组合”
- 结构化问答(带引用)
- temperature:0.2~0.4
- max_tokens:400~800(视你的 UI 长度)
- RAG:TopK=10,重排后 TopN=3~5(塞进 Prompt)
- 规则:必须引用;无证据拒答;输出固定结构
- 创意写作(不需要引用)
- temperature:0.8~1.0
- max_tokens:800~1500
- 规则:先给大纲再展开,避免跑题
最小可运行 Demo(LLM 基础版)
目标:用一个最小脚本跑通“预算估算 → 调用参数 → 输出校验”的闭环。
最小可运行代码(Token 预算 + 调用参数示例)
// demo-llm-budget.ts(伪代码)
function roughTokenEstimate(text: string) {
// 粗略估算:中文按 1 字 ≈ 1.3 token,英文按 1 单词 ≈ 1 token
const chineseChars = text.match(/[\u4e00-\u9fff]/g)?.length || 0;
const words = text.split(/\s+/).filter(Boolean).length;
return Math.ceil(chineseChars * 1.3 + words);
}
const system = "你是严格助手,必须引用来源。";
const user = "公司年假怎么计算?";
const rag = "证据片段..."; // 模拟RAG
const inputTokens =
roughTokenEstimate(system) + roughTokenEstimate(user) + roughTokenEstimate(rag);
const maxTokens = 600;
console.log("inputTokens:", inputTokens);
console.log("maxTokens:", maxTokens);
完整 Demo 结构(LLM 基础最小骨架)
demo-llm-basic/
scripts/
demo-llm-budget.ts
README.md
常见坑排查清单
- 预算超标:RAG 片段过长 → 减少 TopN 或做摘要
- 温度太高:结构化输出变得不稳定 → 降到 0.2~0.4
- 引用缺失:Prompt 未强制引用 → 加“必须引用”规则
完整可运行代码(Token/上下文估算)
源码目录:
docs/demos/token-demo
node docs/demos/token-demo/index.js