前端基础
HTML / CSS / JavaScript、React / Vue、TypeScript、状态管理与工程化。
WEEK 01 / HANDS-ON
面向已有 React / TypeScript 基础的前端开发者。目标不是“看懂概念”,而是亲手完成一个支持流式回复、上下文对话和工具调用的可运行项目。
16-WEEK / PORTFOLIO-FIRST
一条以作品为中心的 16 周路线。点击任意技能,查看精选资料、实战任务和可验证的完成标准。
把模型返回、工具参数与业务状态类型化
为聊天消息、模型配置、工具调用定义类型;核心代码不使用 any
实现 SSE、停止生成、重试和会话状态
可停止、可重试的流式聊天页,刷新后会话保留
把 Web 前端扩展为可安装桌面应用
解释主/渲染进程边界;受限 IPC 调用本地能力并打包
掌握消息、Token、流式响应和模型切换
同一界面可切换两种模型;正确处理限流、超时、鉴权错误
设计清晰指令、示例、边界和结构化输出
20 条固定测试样例;结构化输出解析成功率 95%
让模型可靠调用真实业务函数
实现天气查询、知识搜索、创建待办三个工具并展示调用过程
理解语义向量、相似度与 Top-K 召回
30 个问题评测集,Top-5 召回命中率 80%
处理 PDF、Markdown、TXT 并合理切分
支持三种文件;能用数据解释块大小、重叠长度为何这样设置
衡量召回、忠实度与拒答表现
回答展示原文引用;无答案时拒答;每次改动可重复跑评测
用图组织状态、节点、路由和循环
实现至少 5 个节点的状态图;能从日志还原每次状态变化
处理短期记忆、摘要和持久会话
长对话 Token 不持续线性增长;新会话与历史会话隔离
为失败、重试、人工确认设计机制
固定 20 个业务任务成功率 80%;敏感动作执行前要求人工确认
集中处理模型鉴权、业务编排和数据适配
API Key 不进入浏览器;接入两个模拟 CRM/OA 接口并统一错误格式
在隐私或离线场景运行开源模型
云端不可用时可切本地模型;明确显示能力差异与运行状态
让项目可复现、可自动检查和发布
Docker Compose 一条命令启动;每次提交自动执行 lint、测试和构建
将前五个阶段合并成一条业务闭环
支持文档问答、引用、3 个工具、多步流程、日志、模型切换和桌面打包
用架构、数据和 Demo 证明真实能力
陌生人按 README 10 分钟跑通;10 分钟讲清架构;3 分钟视频展示闭环
CAPSTONE
不要做六个零散 Demo。把所有技能集中进一个能被面试官实际操作、追问和验证的产品。
EXECUTION RULES
岗位目标不只是“会调一个模型接口”,而是能把模型、Agent、知识库与产品界面组合成稳定可交付的应用。本教程先攻下第 3 项,其余模块按依赖顺序继续推进。
HTML / CSS / JavaScript、React / Vue、TypeScript、状态管理与工程化。
主进程与渲染进程、IPC 通信、安全边界、打包与分发。
OpenAI / 通义 / 文心 / DeepSeek API,Ollama / llama.cpp 本地模型,Prompt、流式响应与上下文管理。
LangChain、LangGraph、AutoGen、CrewAI,工具调用、多步骤编排与可靠性。
Embedding、文档解析切分、Milvus / Chroma / Weaviate、向量检索与效果评估。
Node.js BFF、Docker、CI/CD 与 Python 服务,完成可部署交付。
聊天交互、加载与错误状态、桌面与移动端适配、可访问性。
AI 编程助手、智能搜索、内容生成;从真实场景出发沉淀作品。
先公开原版课程的缺口,再说明这次如何修复。你可以用这张表反查后面的内容,而不是只相信“已经优化”。
第 0 天建立底层模型,随后保留原来的 7 天实作结构。每天只解决一个问题;当天代码能跑,再进入下一天。
从文字被分词开始,走过上下文、概率分布、采样、SSE 传输与客户端执行循环。完成第 0 天后,再进入下面 7 天实作。
复制代码是起点,不是终点。每跑通一段,都改一个变量、制造一次错误、再自己修复,才能建立真正的调试能力。
先建立一张因果图:文本被切成 token → token 进入有限上下文 → 模型计算下一个 token 的概率 → 采样得到输出 → 客户端接收流并决定是否执行工具。
前端类比:字符编码 + 编译器词法分析
模型不直接读取“字”或“单词”,而是把文本映射成词表里的整数 ID。一个 token 可能是一个汉字、词片段、标点或空格;同一句话在不同 tokenizer 下数量可能不同。模型真正处理的是 ID 序列,因此费用、延迟和上下文占用通常按 token 计算,而不是按字符数计算。
前端类比:内存预算,不是硬盘
上下文窗口是一次推理中可见的 token 总预算,通常同时包含 system、历史消息、工具定义、检索内容和待生成输出。模型没有自动读取你上一次请求的“长期记忆”;你不重发,它就看不见。
前端类比:对候选列表先改权重,再过滤
模型先为“下一个 token”产生一组分数(logits),经 softmax 变成概率。temperature 缩放分数:接近 0 时分布更尖、更偏向最高概率;升高后分布更平,低概率候选更有机会。top_p 则从高到低保留累计概率达到阈值的最小候选集合,再在其中采样。
前端类比:高优先级配置 + 接口契约
聊天模型在训练阶段学会区分 system、user、assistant 等角色,因此 system 内容会作为高优先级条件影响后续 token 的概率分布。它有效,是因为模型被训练去遵循这种消息结构,不是因为存在一个绝对安全的“系统权限”。
前端类比:单向事件流,对照 WebSocket 双向连接
SSE 是一个保持打开的 HTTP 响应,服务端按 data: 事件持续写入文本,客户端边读边渲染。聊天生成主要是服务端向客户端单向推送,因此无需为双向实时通信引入 WebSocket 的协议和连接管理成本。
前端类比:事件回调 + dispatcher
模型不会进入你的运行时调用函数。它只是根据工具 schema 生成结构化文本:工具名和参数。客户端像事件分发器一样校验意图、调用白名单函数、把结果作为 tool 消息回传,再让模型生成最终回答。
完成证据:你能不看页面,用 2 分钟向面试官解释这张图,并说明为什么 Function Calling 不是模型执行代码。
我们采用 React + Vite 做界面,Node.js 做轻量代理。浏览器永远不直接持有 API Key。
src/ 放 React 界面,server/ 放代理接口,.env.local 只存本机密钥。前后端分开,是为了避免把密钥打进浏览器代码。
npm create vite@latest llm-chat -- --template react-ts
cd llm-chat
npm install
npm install express cors dotenv
npm install -D tsx @types/express @types/cors
mkdir serverVite 提供最小 React + TypeScript 客户端;Express 只承担密钥隔离和流转发。先把浏览器职责与可信服务端边界分开,后面替换模型供应商时无需改界面。
DEEPSEEK_API_KEY=替换成你的密钥.env.local 是运行时配置,不是前端源码。它让服务端读取密钥,而不会被 Vite 打包进可下载的 JavaScript;这和前端不能保存数据库密码是同一条信任边界。
不要写进 src/,不要使用 VITE_ 前缀,也不要提交到 Git。确认 .gitignore 包含 .env*。
npm run build。dist/ 内搜索密钥前 6 位和 DEEPSEEK_API_KEY。完成证据:保留终端“0 个匹配”的截图,写进 README 的安全说明。
Day 1 不写 UI。先用 curl 验证密钥、网络、模型名和请求格式都正确。
# 让当前终端加载 .env.local,再执行请求
set -a
source .env.local
set +a
curl https://api.deepseek.com/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $DEEPSEEK_API_KEY" \
-d '{
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": "你是一个简洁的编程助教。"},
{"role": "user", "content": "用一句话解释什么是 token。"}
],
"stream": false
}'source .env.local 先把密钥载入当前 shell;文件本身不会自动变成环境变量。再用 curl 绕过 UI 与状态管理,可把故障范围缩到鉴权、网络、模型名或请求体。PowerShell 可先运行 $env:DEEPSEEK_API_KEY="你的密钥"。
HTTP 200,返回 JSON,并且 choices[0].message.content 中出现回答。
401 检查 Key;429 检查余额或频率;连接失败先排查网络与代理;400 对照响应里的错误信息检查请求体。
如果电脑配置允许,可用 Ollama 先跑一个小模型,理解“云端 API”与“本地推理”的差别。llama.cpp 适合后续进一步研究量化格式与推理参数。
ollama pull qwen2.5:3b
ollama run qwen2.5:3b "用一句话解释什么是 token"
# 查看本地 OpenAI 兼容接口是否可用
curl http://localhost:11434/api/chat \
-d '{"model":"qwen2.5:3b","messages":[{"role":"user","content":"你好"}]}'本地模型把“网络服务质量”和“模型推理能力”拆开观察。相同 messages 结构可以帮助你比较云端与本地的延迟、输出质量和资源成本,而不是把模型名写死在 React 组件里。
把模型地址、模型名和鉴权方式放进服务端配置层,React 侧只调用自己的 /api/chat,以后切换通义、文心、OpenAI 或本地模型时不用重写 UI。
temperature: 0,连续请求 3 次。temperature: 1,再请求 3 次;其余参数保持不变。完成证据:一张 6 次输出对照表。不要只写“更有创造力”,要指出哪些 token 选择变得不稳定。
关键路径只有三段:浏览器发消息 → Node 转发请求 → 浏览器逐块读取 SSE。
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
dotenv.config({ path: ".env.local" });
const app = express();
app.use(cors());
app.use(express.json());
app.post("/api/chat", async (req, res) => {
try {
const upstream = await fetch(
"https://api.deepseek.com/chat/completions",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.DEEPSEEK_API_KEY}`,
},
body: JSON.stringify({
model: "deepseek-chat",
messages: req.body.messages,
stream: true,
}),
}
);
if (!upstream.ok || !upstream.body) {
return res.status(upstream.status).send(await upstream.text());
}
res.setHeader("Content-Type", "text/event-stream");
const reader = upstream.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
res.write(Buffer.from(value));
}
res.end();
} catch (error) {
res.status(500).json({ error: "代理请求失败" });
}
});
app.listen(3001, () => console.log("API: http://localhost:3001"));代理不解析模型内容,只保管密钥、检查上游状态并把字节流向下游转发。res.write 保持响应开放,避免等待完整回答;upstream.ok 则阻止把错误页伪装成正常 SSE。
新开一个终端运行 npx tsx server/index.ts。浏览器端请求 http://localhost:3001/api/chat。
async function streamReply(messages: Message[], onToken: (t: string) => void) {
const response = await fetch("http://localhost:3001/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages }),
});
if (!response.ok || !response.body) throw new Error("请求失败");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const data = line.slice(6);
if (data === "[DONE]") return;
const token = JSON.parse(data).choices[0]?.delta?.content;
if (token) onToken(token);
}
}
}getReader() 读取的是字节块,TextDecoder 负责保持跨 chunk 的 UTF-8 解码状态,buffer 负责保持跨 chunk 的事件状态。两层状态都保留,中文字符或 JSON 被截断时才不会随机报错。
网络分块不保证刚好按一行切开。一个 JSON 片段可能跨两个 chunk;先累积,再按换行取完整事件,才能避免随机解析失败。
fetch 传入 AbortController.signal,界面增加“停止生成”。AbortError,保留已收到文本并标记“已中断”。完成证据:录一段 15 秒视频,包含生成、停止、显示中断态、再次发送四步。
对话上下文不是模型自动记住的;每次请求都要把需要的历史消息一起发送。
import { useState, type FormEvent } from "react";
type Message = {
role: "system" | "user" | "assistant";
content: string;
};
async function streamReply(messages: Message[], onToken: (token: string) => void) {
const response = await fetch("http://localhost:3001/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages }),
});
if (!response.ok || !response.body) throw new Error("请求失败");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const data = line.slice(6);
if (data === "[DONE]") return;
const token = JSON.parse(data).choices[0]?.delta?.content;
if (token) onToken(token);
}
}
}
export default function App() {
const [messages, setMessages] = useState<Message[]>([
{ role: "system", content: "你是简洁、准确的编程助教。" },
]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
async function send(event: FormEvent) {
event.preventDefault();
const text = input.trim();
if (!text || loading) return;
const userMessage: Message = { role: "user", content: text };
const next: Message[] = [...messages, userMessage];
setInput("");
setError("");
setLoading(true);
setMessages([...next, { role: "assistant", content: "" }]);
try {
await streamReply(next, (token) => {
setMessages((current) => current.map((message, index) =>
index === current.length - 1
? { ...message, content: message.content + token }
: message
));
});
} catch (cause) {
setError(cause instanceof Error ? cause.message : "未知错误");
} finally {
setLoading(false);
}
}
return (
<main>
{messages.filter((message) => message.role !== "system").map((message, index) => (
<p key={index}><b>{message.role}:</b> {message.content}</p>
))}
{error && <p role="alert">{error}</p>}
<form onSubmit={send}>
<input value={input} onChange={(event) => setInput(event.target.value)} disabled={loading} />
<button disabled={loading || !input.trim()}>{loading ? "生成中…" : "发送"}</button>
</form>
</main>
);
}messages 是发给模型的临时工作内存,类似 Redux 中的显式状态:每次请求都提交一个确定快照。先插入空 assistant 项,再逐 token 更新最后一项,可让 UI 只维护一条正在增长的消息。
先写清角色、任务、约束与输出格式,再用输入样例测试。提示词和代码一样要能版本化、能回归验证。
export const systemPrompt = `你是 TypeScript 编程助教。
任务:解释用户给出的代码问题,并给出可运行的最小修改。
约束:不猜测缺失信息;不确定时明确说明;不暴露密钥。
输出:先给结论,再给代码,最后列出 1 条验证命令。`;角色、任务、约束、格式分别减少不同的不确定性,相当于给模型定义 TypeScript 接口;但模型输出仍是概率结果,所以“接口契约”需要测试与校验,不能当成编译器保证。
登录、数据库、Markdown 高亮、聊天记录持久化都可以后补。第一周最重要的是请求链路与状态管理清楚。
完成证据:README 写出你的裁剪规则、预算值和一个“丢失信息”的已知限制。
模型不会直接执行函数。它只输出一个结构化的调用意图,你的代码验证参数、执行函数,再把结果交回模型。
const tools = [{
type: "function",
function: {
name: "calculate",
description: "计算两个数字的加减乘除",
parameters: {
type: "object",
properties: {
a: { type: "number" },
b: { type: "number" },
operation: {
type: "string",
enum: ["add", "subtract", "multiply", "divide"]
}
},
required: ["a", "b", "operation"]
}
}
}];schema 是模型与执行器共享的调用协议,类似前端事件的 payload 类型。枚举收窄候选动作,required 保证必要字段存在;真正运行前仍要在服务端做运行时校验,因为模型生成的 JSON 不受 TypeScript 类型系统保护。
执行前必须校验工具名、参数类型与范围。涉及写文件、发消息、付款等操作时,还需要明确的人类确认;不要让模型自由拼接并执行 shell 命令。
tools。tool_calls,由你的代码解析参数并执行对应函数。tool 的结果都追加进 messages。完成证据:保留成功与失败两条 trace;你要能指出模型负责决策、程序负责执行的边界。
能运行只是起点。把项目整理到陌生人 5 分钟内可以理解、启动和验证。
完成证据:一个可运行仓库、一段演示视频、一条有分析过程的故障记录。它们比答对选择题更能证明能力。
对应 JD:大模型 API 接入、Prompt 工程、流式响应、上下文管理、Function Calling、React/TypeScript 工程化与错误处理。
题库范围:LLM 工具调用 16 题;大模型工程中的采样参数、Prompt、CoT、幻觉、KV Cache / Prompt Caching 与 vLLM / llama.cpp 部署选型。
面试官会问:为什么用 SSE 而不是 WebSocket?上下文窗口满了怎么处理?模型真的执行了函数吗?如何防止 API Key 泄露?temperature 与 top_p 分别改变什么?
回答结构:先讲底层机制,再讲你的选型与失败案例,最后展示 trace、README 或演示链接。不要只背定义。
在这个项目上继续增加文档切分、Embedding、向量检索和引用来源。不要另起炉灶——让作品沿着一条清晰路线持续进化。
前面 7 天你学会了把模型接进产品;FDE 解决的是下一个问题——把 AI 送进真实业务,让它产生可验证的结果。这是目前西安 AI 岗里薪资天花板最高、和你转型路线最贴合的方向。
一句话定位:Forward Deployed Engineer,以客户业务结果为目标、仍然直接写生产代码的工程岗。日常不在训练模型,而在做四件事:发现场景、研判可行性、跑通 POC、推广复制。和面试备战 · FDE 专题配合看:那边是面试怎么答,这里是能力怎么练。
| 能力 | 在干什么 | 对应你教程里的模块 |
|---|---|---|
| 场景洞察 | 把“我们想用 AI”翻译成明确的用户、流程、约束和可验证目标。 | DAY 00 底层概念(理解模型能力边界)+阶段一作品的需求定义 |
| 技术可行性研判与选型 | 效果评估、方案选型:RAG 还是微调?Agent 还是工作流?成本时延能不能接受? | RAG(切分/召回/重排/评测)+ Agent(任务拆分/记忆/规划)+部署选型 |
| POC 落地与闭环 | 结合场景跑通创新试点,收集业务反馈,跟进问题闭环、提升效果。 | 第 7 天验收标准(陌生人 5 分钟可理解/启动/验证)+故障复现记录 |
| 推广布道 | 沉淀场景案例与落地经验,把方案在更大范围复制推广。 | README(架构/选型/限制/下一步)+演示视频 |
① 评测体系:给 AI 方案定指标(任务成功率/时延/成本/采用率)并做对比验证——RAG 评测模块补上;② POC 方法论:小流量试点、基线对照、回滚条件,写成你自己的检查表;③ 业务语言:把技术方案翻译成业务收益,这是 FDE 面试必考的。学历标签(硕士)先直接问招聘者确认,不要自己脑补被卡。
猎聘全国口径 FDE 平均年薪 40.8 万;西安实测:联东集团 AI Agent 工程师 25–40k·15薪、西安博达软件大模型应用开发 18–28k·13薪、道通科技 Agent 应用工程师 11–29k·13薪。对比纯前端中枢 1.0–1.4 万/月——这就是 A 轨(AI 应用/FDE)薪资天花板更高的实测证据。
完成证据:一页场景洞察文档、一个可运行的 POC、一条故障记录、一页推广复盘。拿着这四样去面 FDE,比“我学过 LangChain”有说服力得多。
FDE 面试题与答题思路见:面试备战 · FDE 专题 · 完整 30+ 道见 Forward Deployed Engineer 专题
这里是题目索引和一句话答题方向,不搬运原站答案。先用项目经验组织自己的答案,再回到原文看图解与追问。
先建立任务循环、记忆、规划和多 Agent 的整体框架。
沿索引、检索、生成、评测顺序,把知识库项目讲完整。
把 Function Calling、MCP、Skill、协议和网关串成一张图。
先看“面试翻车现场”,明确答不清会被追问什么;再看图解,建立因果链;最后合上页面,用“定义 → 取舍 → 项目证据”复述。应用开发岗主攻 Agent、RAG、工具调用,约 1 个月完成首轮。
阶段一 · 大模型应用:工具调用 16 题,加大模型工程中的采样参数、Prompt、CoT、幻觉、KV Cache、部署选型。
阶段二 · RAG:RAG 20 题,答案必须带你的切分、召回、重排和评测证据。
阶段三 · Agent:Agent 16 题,重点讲任务拆分、记忆、规划、反思和多 Agent 选型。
算法岗进阶选看:Transformer、Flash Attention、RoPE、LoRA、RLHF / DPO / GRPO、量化、MoE;应用岗先会解释边界,不必先钻训练公式。
同一资源页还有图解 Agent 专栏 6 篇、图解 Claude Code 专栏 10 篇及 LangChain 框架专题。另可用开源题库按项目链路补充练习。
应用开发主考什么?
优先准备 Agent、RAG、工具调用三块,并把每个答案落到自己的项目证据。
工程底层题都要看吗?
采样、Prompt、幻觉、缓存和部署必看;Transformer、训练对齐、量化、MoE 可作为算法岗进阶。
怎样避免背完就忘?
每题录一次 90 秒口述,再让自己追问“为什么、怎么选、失败怎么办”。
先把简历写成可核验的工程证据,再把其中三个项目或场景整理成能口述的 STAR 稿。
姓名:______ | 城市:______ | 电话:______ | 邮箱:______ | GitHub:______ | 学历:______
目标职位:□ AI 应用开发工程师 □ AI Agent 开发工程师 | 期望城市:______ | 经验:______ | 到岗时间:______
项目名:______ 时间:______ 角色:______
项目描述:用一句话写清用户、问题和产出:______
技术架构:接入层 ______ / 应用层 ______ / AI 能力层 ______ / 数据层 ______ / 基础设施 ______
关键技术决策:选择 ______ 而不是 ______,因为约束/指标是 ______
可量化结果:将 ______ 从 ______ 改善到 ______;测量口径 ______
至少覆盖:一个完整 AI 项目、一次复杂故障或失败修复、一次需求冲突或跨角色推进。每份准备 30 秒结论版、3 分钟展开版和追问证据。
完整简历模板与更多示例见:简历面试技巧 · AI Agent 简历模板
一句话定位:以客户业务结果为目标、仍然直接写生产代码的工程岗位;核心是从模糊问题出发完成需求发现、方案设计、编码集成、生产上线、采用推广和反馈闭环。
| 能力维度 | 面试官想确认什么 |
|---|---|
| 问题发现与拆解 | 能否把“我们想用 AI”转成明确用户、现有流程、约束条件和可验证目标。 |
| 工程与 AI 系统设计 | 能否设计可接入现有系统、可评测、可观测且可回滚的生产方案。 |
| 编码与调试 | 能否在陌生代码、脏数据和变化需求下快速定位问题并可靠实现。 |
| 客户与项目推进 | 能否管理承诺、冲突、范围、采用过程和高压事故,而不丢失业务目标。 |
| 产品化反馈 | 能否区分一次性定制与可复用能力,并把现场反馈沉淀进产品。 |
完整 30+ 道 FDE 面试题见:Forward Deployed Engineer 专题
先按目标选择题库,再回到项目补证据。资源说明只做定位,不替代仓库原文。
| 资源名 | 一句话介绍 | 链接 | 适合人群 |
|---|---|---|---|
| guocong-bincai/ai-interview-guide | 720 道题 / 28 个专题,覆盖 AI 应用、RAG、Agent、FDE、多模态、安全评测,配图解。 | 打开 GitHub | 需要系统刷题的人 |
| bcefghj/ai-agent-interview-guide | 200+ Agent 面试题 + 企业级项目(Python / Java / Go 三版)+ 简历模板。 | 打开 GitHub | 项目深挖和简历准备 |
| zhouzhupianbei/AgentInterview | AIGC 开发者一站式面试准备资源。 | 打开 GitHub | 想快速定位复习入口的人 |
| didilili/ai-agents-from-zero | AI 智能体与大模型应用开发面试题库;本站面试备战已收录,列出以便对照。 | 打开 GitHub | 已有本站基础内容、需要对照复习的人 |