Ai 相关 7月1日学习

AI 开发相关面试题整理(含 Demo + 深追模拟)

每题三层:① 核心答案 ② 可运行 Demo ③ 深追 Q&A 模拟对话


1. GPT / Gemini / Claude 各自什么脾性,怎么搭配多模型工作流

核心答案

模型 优势 弱点
GPT-4o 生态广、工具调用稳、多模态强、响应快 有时过度取悦用户,长推理深度不足
Gemini 2.0 超长上下文(100万 token)、Google 生态集成好 指令跟随有时漂移,代码稳定性稍逊
Claude Sonnet/Opus 指令跟随精准、代码质量高、长文本处理强 工具生态相对小,有时过于谨慎

选型口诀: Claude 做事、GPT 审查、Gemini 塞料(超长上下文)。

多模型协作架构:

less 复制代码
Claude Code(4-5 tab 并行实施)
  ├── Tab A: feature/search 实施
  ├── Tab B: feature/auth 实施
  └── Tab C: 方案设计 / 架构评审

Codex / GPT-4(独立 session)
  └── 对 Tab A/B 产出做批判性 Review
      ------ 不共享上下文,保证独立性

工作流沉淀:

  • CLAUDE.md:项目级规范常驻上下文
  • Slash command(/writer/review):封装高频 prompt
  • Skill 系统:跨项目最佳实践复用
  • Hooks:pre-commit 自动注入检查

🔧 Demo:用 OpenRouter 统一接入多模型

typescript 复制代码
// openrouter-demo.ts  ------  一个 key 路由到不同模型
// 运行:npx ts-node openrouter-demo.ts

const BASE = 'https://openrouter.ai/api/v1/chat/completions';
const KEY  = process.env.OPENROUTER_API_KEY!;

async function callModel(model: string, prompt: string) {
  const res = await fetch(BASE, {
    method: 'POST',
    headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model,
      messages: [{ role: 'user', content: prompt }],
    }),
  });
  const data = await res.json();
  return data.choices[0].message.content as string;
}

async function reviewWorkflow(task: string) {
  console.log('=== Step 1: Claude 实施 ===');
  const implementation = await callModel(
    'anthropic/claude-sonnet-4-5',
    `请实现以下功能,只输出代码:${task}`
  );
  console.log(implementation);

  console.log('\n=== Step 2: GPT-4 Review ===');
  const review = await callModel(
    'openai/gpt-4o',
    `请对以下代码做批判性 Review,列出潜在问题:\n\n${implementation}`
  );
  console.log(review);
}

reviewWorkflow('实现一个带防抖的搜索输入框 React Hook');

💬 深追 Q&A 模拟

Q:你怎么保证两个模型不会互相影响,比如 GPT review 时不会被 Claude 的实现思路带偏?

关键是上下文隔离。GPT review session 只拿到最终代码产物,不拿到 Claude 的思考过程和中间对话。我的做法是 Claude 做完后把代码复制到新的 GPT session,prompt 里只给代码 + "请独立批判性 review",不带任何 Claude 的解释。这样 GPT 是在用自己的视角评估,而不是在帮 Claude 辩护。

Q:你说 Claude 指令跟随精准,能举个具体例子吗?

我有个场景:让 AI 只修改某一个函数,不要动其他文件。Claude 基本能严格遵守,GPT-4 有时会"顺手"改几个它觉得有问题的地方。在多文件项目里这个差异影响很大,因为你不知道它改了什么,review 成本就高了。

Q:Gemini 超长上下文你实际用过吗,有没有掉坑?

用过,主要是把整个 repo 塞进去问架构问题。坑是:context 越长,模型越容易"迷失",对早期信息的注意力下降。实测超过 20 万 token 后,回答的准确性会有肉眼可见的下降,所以我不会无脑塞,会先做文件筛选,只塞相关模块。


2. AI 上下文治理(污染、回退、Sub Agent 隔离、压缩)

核心答案

问题 策略
上下文污染 新开对话 / /clear / compact;只把 relevant 内容放进来
Sub Agent 隔离 子任务起独立 Agent,父 Agent 只看摘要结果
上下文压缩 超长对话让模型总结当前状态,新开对话粘摘要续跑
Skill 注入 规范外置到 skill 文件,按需注入,不污染主对话
版本回退 重要节点 commit,AI 出问题直接 git checkout

🔧 Demo:CLAUDE.md 项目规范模板(最小可用版)

markdown 复制代码
<!-- .claude/CLAUDE.md  ------  放在项目根目录,Claude Code 自动读取 -->

# 项目规范

## 技术栈
- React 18 + TypeScript 5 + Vite
- 状态管理:Zustand(禁止引入 Redux)
- 样式:Tailwind CSS(禁止写 inline style)
- 请求:TanStack Query v5

## 目录约定
- 组件放 src/components,每个组件一个目录(index.tsx + index.module.css)
- 业务 hook 放 src/hooks,以 use 开头
- 工具函数放 src/utils,纯函数,不引入 React

## 禁止事项
- 禁止在组件内直接 fetch,必须通过 TanStack Query
- 禁止使用 any,必须定义具体类型
- 禁止修改 src/api 目录下的文件,那是自动生成的

## 提交规范
- feat: / fix: / refactor: / chore: 前缀
- 每个 PR 只做一件事

🔧 Demo:pre-commit hook 自动检查(最小复现)

bash 复制代码
# .husky/pre-commit
#!/bin/sh

echo "Running pre-commit checks..."

# TypeScript 类型检查
npx tsc --noEmit
if [ $? -ne 0 ]; then
  echo "❌ TypeScript 类型错误,请修复后再提交"
  exit 1
fi

# ESLint
npx eslint src --ext .ts,.tsx --max-warnings 0
if [ $? -ne 0 ]; then
  echo "❌ ESLint 错误,请修复后再提交"
  exit 1
fi

echo "✅ 检查通过"

🔧 Demo:Sub Agent 隔离模式(Claude Code Task 工具示意)

bash 复制代码
# 父 Agent prompt(in CLAUDE.md or system prompt):
你是一个 orchestrator。将以下需求拆成独立子任务,
每个子任务用 Task 工具启动独立 Agent 执行,
只关注最终产物,不关注执行细节。

# 子 Agent 只拿到:
任务描述 + 相关文件 + 完成标准
------ 不拿到父 Agent 的对话历史

# 父 Agent 收到:
执行摘要 + 修改的文件列表
------ 不拿到子 Agent 的中间推理

💬 深追 Q&A 模拟

Q:上下文污染你是怎么发现的,有没有具体现象?

最明显的现象是 AI 开始"记仇"。比如我之前尝试了一个错误的方案,后来换了正确方向,但 AI 还是会时不时往错误方向走,因为它的上下文里有大量失败的尝试。另一个现象是回答越来越长、越来越啰嗦,像是在不停 recap 之前说过的内容。这时候我就新开对话,只粘贴当前有效的代码和下一步目标。

Q:Sub Agent 隔离在工程上怎么实现,不是随便说说的那种?

用 Claude Code 的 Task 工具,父 Agent 通过 Task 启动子 Agent,子 Agent 有自己独立的上下文窗口。父 Agent 的 prompt 里我明确写"不要自己动手,用 Task 工具分发",子 Agent 完成后只返回执行摘要。如果不用 Claude Code,手工实现就是:每个子任务开一个新的 API 调用,system prompt 只带当前子任务的上下文,结果聚合在父层处理。

Q:compact / 压缩之后信息会丢失吗,你怎么保证关键决策不丢?

会丢,这是压缩的代价。我的做法是在做重要架构决策的时候,让 AI 生成一个"决策记录"(类似 ADR),写进 CLAUDE.md 或单独的 decisions.md。这样压缩上下文后,决策记录还在文件系统里,下次对话 AI 读文件就能恢复上下文,而不是靠对话历史。


3. 公司有使用 AI 提效赋能吗

核心答案

五个维度展开,最好有量化数据:

  1. 代码层面:Claude Code 实施 + Codex Review,并行多 tab
  2. 流程层面:slash command、skill 系统沉淀高频 prompt
  3. 文档层面:AI 生成技术文档、PRD 初稿、commit message
  4. 质量层面:AI 辅助写单测、安全扫描
  5. 工作流层面CLAUDE.md + hooks 自动化检查

🔧 Demo:slash command /review 示例

markdown 复制代码
<!-- .claude/commands/review.md  ------  /review 命令 -->

请对当前改动做代码 Review,按以下维度输出:

## 正确性
- 逻辑是否有 bug?
- 边界情况是否处理?

## 安全性
- 有无 XSS / CSRF / SQL 注入风险?
- 用户输入是否做了校验?

## 性能
- 有无不必要的重渲染?
- 有无内存泄漏风险?

## 可维护性
- 命名是否清晰?
- 是否有魔法数字需要提取为常量?

最后给出:🔴 必须修复 / 🟡 建议修改 / 🟢 可以接受

🔧 Demo:AI 生成 commit message 的 hook

bash 复制代码
# .husky/prepare-commit-msg
#!/bin/sh

COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2

# 只对空 commit message 生效(用户没有手动填写时)
if [ -z "$COMMIT_SOURCE" ]; then
  DIFF=$(git diff --cached --stat)
  # 调用 AI 生成 commit message(示例用 Claude API)
  MSG=$(curl -s https://api.anthropic.com/v1/messages \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d "{
      "model": "claude-haiku-4-5",
      "max_tokens": 100,
      "messages": [{"role": "user", "content": "根据以下 git diff stat 生成一行 conventional commit message(feat/fix/refactor/chore: 描述):\n$DIFF"}]
    }" | jq -r '.content[0].text')
  echo "$MSG" > "$COMMIT_MSG_FILE"
fi

💬 深追 Q&A 模拟

Q:你说 AI 帮你写单测,测试覆盖率真的能提升吗,AI 写的测试质量怎么样?

覆盖率是提升了,但质量要看提示词。如果让 AI "给这个函数写单测",它通常只测 happy path,边界情况覆盖不足。我改成让它"写单测,必须包含:正常情况、边界值、错误情况、异步失败情况",质量明显好很多。另外 AI 对 mock 的使用经常过度,我会让它"尽量少 mock,优先用真实逻辑"来约束。

Q:量化收益你说"从两天缩到半天",这怎么衡量的,有可信度吗?

这个主要靠对比,同类型的功能开发,AI 介入前后的 PR 提交时间对比。严格来说不科学,因为功能复杂度不同。更客观的是看 review 轮次:我们组引入 AI 辅助 review 之后,平均 PR review 轮次从 3.2 次降到 1.8 次,这个是有记录的。


4. Skill 系统设计(发现、注册、热更新、隔离)

核心答案

Skill 系统本质是可插拔的 prompt 模块化 + 上下文注入管道,架构类似插件系统。

阶段 实现方式
发现 扫描约定目录,找 SKILL.md;优先级:项目级 > 用户级 > 系统级
注册 元数据(name/description/trigger)+ 内容 → 写入内存 registry
热更新 fs.watch 监听目录,变更时重新解析,hash 比对避免无效重载
隔离 独立上下文沙箱;最小工具集(默认只 read);Sub Agent 模式

🔧 Demo:一个最小 SKILL.md

yaml 复制代码
---
name: react-component
description: 生成符合项目规范的 React 函数组件,包含 TypeScript 类型、样式模块、单测
trigger: /component
---

## 使用方式
调用时提供组件名称和功能描述。

## 输出规范
1. `ComponentName/index.tsx` --- 组件本体
2. `ComponentName/index.module.css` --- 样式(Tailwind 优先,复杂样式用 module)
3. `ComponentName/index.test.tsx` --- 单测(vitest + @testing-library/react)

## 代码规范
- Props 接口命名为 `ComponentNameProps`
- 必须有 JSDoc 注释描述组件用途
- 不使用 default export,使用 named export
- 异步操作必须有 loading / error 状态

## 示例结构
```tsx
export interface SearchBoxProps {
  /** 搜索结果回调 */
  onSearch: (keyword: string) => void;
  placeholder?: string;
}

export function SearchBox({ onSearch, placeholder = '搜索...' }: SearchBoxProps) {
  // 实现
}
shell 复制代码
### 🔧 Demo:最小 Skill 热更新 Watcher(Node.js)

// skill-watcher.ts ------ npx ts-node skill-watcher.ts import fs from 'fs'; import path from 'path'; import crypto from 'crypto';

interface Skill { name: string; description: string; content: string; hash: string; }

const registry = new Map<string, Skill>(); const SKILLS_DIR = path.resolve('.claude/skills');

function parseSkill(filePath: string): Skill | null { try { const raw = fs.readFileSync(filePath, 'utf-8'); // 解析 frontmatter const match = raw.match(/^---\n(\\s\\S?)\n---\n(\\s\\S)$/); if (!match) return null; const meta: Record<string, string> = {}; match1.split('\n').forEach(line => { const k, ...v = line.split(':'); if (k) metak.trim() = v.join(':').trim(); }); return { name: meta.name, description: meta.description, content: match2, hash: crypto.createHash('md5').update(raw).digest('hex'), }; } catch { return null; } }

function loadSkills() { if (!fs.existsSync(SKILLS_DIR)) return; fs.readdirSync(SKILLS_DIR) .filter(f => f.endsWith('.md')) .forEach(f => { const skill = parseSkill(path.join(SKILLS_DIR, f)); if (skill) { registry.set(skill.name, skill); console.log([skill] loaded: ${skill.name}); } }); }

function watchSkills() { loadSkills(); fs.watch(SKILLS_DIR, (event, filename) => { if (!filename?.endsWith('.md')) return; const filePath = path.join(SKILLS_DIR, filename); const skill = parseSkill(filePath); if (!skill) return; const existing = registry.get(skill.name); // hash 比对,避免无效重载 if (existing?.hash === skill.hash) return; registry.set(skill.name, skill); console.log([skill] hot-reloaded: ${skill.name}); }); console.log(Watching ${SKILLS_DIR} for skill changes...); }

watchSkills(); // registry 现在随时可以查询 export { registry };

markdown 复制代码
* * *

### 💬 深追 Q&A 模拟

**Q:Skill 发现用约定目录扫描,如果 skill 名字冲突了怎么办?**

> 优先级规则:项目级 > 用户级 > 系统级,就近原则,同层级里后加载的覆盖先加载的。实际上在描述字段上也可以做 namespace,比如 `react:component` 和 `vue:component`,registry 的 key 带 namespace 就不冲突了。

**Q:热更新你用 `fs.watch`,这个在 macOS 上不可靠是众所周知的问题,你怎么处理?**

> 确实,`fs.watch` 在 macOS 上有时会漏事件,生产级别的方案要用 `chokidar`,它内部在不同平台用不同的 API(macOS 用 FSEvents,Linux 用 inotify),稳定性好很多。另外加一个 polling fallback,每隔 30 秒全量重扫一次作为兜底。

**Q:隔离你说"独立上下文沙箱",上下文泄漏的场景你遇到过吗?**

> 遇到过。最典型的是 skill A 里注入了一个"你是一个严格的代码审查者"的角色设定,没有正确清理,导致后续普通对话 AI 也开始过度挑剔。解法是 skill 执行完成后,发一条 reset prompt:"现在 skill 执行结束,回到默认角色",或者更彻底的方案是用独立的 Agent 实例执行 skill,天然隔离。

* * *

## 5. 你平常用什么 Vibecoding 工具

### 核心答案

-   **主力实施**:Claude Code(CLI)------ 精准指令跟随,代码质量高
-   **深度 Review**:Codex ------ 思考更深,适合批判性审查
-   **IDE 内嵌**:Cursor / Windsurf ------ 需要在编辑器里操作时
-   **行级补全**:GitHub Copilot ------ 低思考成本的自动补全
-   **并行模式**:同时开 4--5 个 Claude Code session,不同子任务并行跑

* * *

### 🔧 Demo:Claude Code 多 tab 并行工作流

Terminal 1 ------ feature/search 实施

cd ~/project claude "实现搜索组件,防抖 300ms,结果展示在 SearchResults 组件里, 样式参考 Figma link,接口文档在 docs/api/search.md"

Terminal 2 ------ feature/auth 实施(并行,互不干扰)

claude "实现登录页面,使用 react-hook-form + zod 做表单校验, 成功后存 token 到 useAuthStore,跳转到 /dashboard"

Terminal 3 ------ Review Tab 1 产出(新 session,独立视角)

claude "请 review src/components/Search 目录下的代码, 重点关注:防抖是否正确、loading 状态是否完整、错误处理是否覆盖"

markdown 复制代码
* * *

### 💬 深追 Q&A 模拟

**Q:多 tab 并行,上下文互相隔离,但如果两个 tab 改了同一个文件怎么办?**

> 这是并行 AI 开发最大的风险,所以任务拆分时要明确文件归属,A tab 负责的文件 B tab 不能碰。我在 prompt 里明确写"只修改以下文件:xxx",并且两个 tab 完成后我手动做 merge review,看有没有冲突。频繁 commit 也是关键,每个 tab 完成一个小里程碑就 commit,这样 git 有完整历史,出现冲突能 diff 追溯。

**Q:Cursor 和 Claude Code 你会怎么选?**

> 场景不同。Cursor 适合需要在编辑器里随时问随时改、上下文是当前打开文件的场景;Claude Code 适合需要精确控制、多文件复杂任务、需要自定义 hooks 和 CLAUDE.md 的场景。我现在主要用 Claude Code,因为它的指令跟随更可控,而且 hooks 和 skill 系统更灵活。

* * *

## 6. 平常是怎么用 Vibecoding 的

### 核心答案

1.  **任务拆解先行**:大需求拆成独立子任务,每个 tab 一块
1.  **CLAUDE.md 注入规范**:架构、命名、禁止事项写进去,每次自动生效
1.  **方案 → 审查分离**:Claude 实施,Codex 独立 review,形成制衡
1.  **自建 slash command**:高频操作封装成 `/review`、`/writer` 等
1.  **hooks 自动化**:pre-commit 触发 lint / typecheck,不手动提醒 AI
1.  **及时 commit**:每个可工作的里程碑就 commit,方便回退

* * *

### 🔧 Demo:一次完整 Vibecoding session 的标准流程

1. 打开项目,Claude Code 自动读取 CLAUDE.md

$ claude

2. 告诉 AI 今天做什么(任务拆解)

今天要做搜索功能,拆成三个子任务:

  1. SearchInput 组件(防抖、清空、loading)
  2. SearchResults 组件(列表渲染、空态、错误态)
  3. useSearch hook(接口调用、状态管理) 先做第 1 个,完成后我告诉你做第 2 个。

3. AI 完成后 review

/review (触发自定义 review slash command)

4. 确认没问题,commit

帮我生成 commit message

5. 开始第 2 个子任务(新 prompt,上下文保持清洁)

现在做 SearchResults 组件,接收 results: SearchResult\[\] 类型, 空态展示 EmptyState 组件,加载中展示 Skeleton,错误态展示 ErrorBoundary

markdown 复制代码
* * *

### 💬 深追 Q&A 模拟

**Q:你说"方案和审查分离",Codex review 完发现问题,你怎么把问题反馈给 Claude?**

> 我不会直接把 Codex 的 review 粘给 Claude,因为这样上下文会变成"Claude 在给自己的代码做辩护",容易有偏见。我的做法是:把 review 里的每个问题,自己消化理解,确认是真问题后,以"这段代码有个问题:......,请修复"的方式告诉 Claude,以问题描述为驱动,而不是以另一个 AI 的评价为驱动。

**Q:及时 commit 这个习惯,AI 改了一大堆文件之前 commit,有时候功能不完整,怎么处理?**

> 用 WIP commit(Work In Progress)。`git commit -m "wip: 搜索组件骨架,功能未完成"`,打上 wip 标签,在 CI 里配置 wip commit 不触发部署流水线。功能完成后 `git rebase -i` 把 wip commits squash 成一个干净的 commit 再 push PR。这样既有安全网,又不污染主线历史。

* * *

## 7. 使用 Vibecoding 时有什么需要注意的

### 核心答案

1.  **上下文管理**:对话过长及时新开,用摘要续接
1.  **不要盲目接受**:安全相关代码(SQL/XSS/权限)必须人工 review
1.  **原子化提交**:改动范围小,方便 review 和回退
1.  **明确约束**:说清楚不能改什么,防止 AI 发挥
1.  **测试同步**:功能和测试同时写,不要事后补
1.  **上下文污染**:失败方向及时清理,开新对话
1.  **保持判断**:架构决策自己拍,AI 给候选方案

* * *

### 🔧 Demo:好 prompt vs 坏 prompt 对比

❌ 坏 prompt ------ 太模糊,AI 会做很多假设

帮我写一个用户管理页面

✅ 好 prompt ------ 明确范围、约束、不做什么

实现 UserManagement 页面,要求:

  • 展示用户列表,使用 UserTable 组件(已存在于 src/components/UserTable)
  • 支持按用户名搜索(防抖 300ms)
  • 支持分页(每页 20 条,使用 Pagination 组件)
  • 只修改 src/pages/UserManagement/index.tsx,不要改其他文件
  • 不需要做新增/编辑/删除功能,那是下个迭代的事
复制代码

❌ 坏 prompt ------ 一次让 AI 改太多

重构整个 src/api 目录,把所有请求从 axios 换成 fetch, 同时加上错误处理、retry 逻辑、鉴权 token 注入

✅ 好 prompt ------ 拆小步骤,逐步推进

先只把 src/api/user.ts 从 axios 换成 fetch, 保持接口签名不变,加上基础的错误处理(4xx/5xx 抛错), 不做 retry 和 token 注入,那是后面的事

markdown 复制代码
* * *

### 💬 深追 Q&A 模拟

**Q:你说安全相关代码必须人工 review,实际上你怎么判断哪些是安全相关的?**

> 我有一个心理 checklist:① 用户输入是否直接拼接到 SQL / HTML / shell 命令 ② 接口有没有鉴权检查 ③ 敏感数据(密码、token)有没有被 log 或暴露在响应里 ④ 文件上传有没有做类型和大小限制。遇到这几类代码,我会暂停,自己逐行读一遍,不依赖 AI 自检------因为 AI 自检的时候它是在验证它自己的逻辑,容易有盲区。

**Q:AI 发挥改了不该改的东西,你怎么快速发现?**

> `git diff --stat` 先看改了哪些文件,如果文件列表里有我没提到的文件,立刻 `git diff 那个文件` 看具体改了什么。所以 atomic commit 非常重要,每次 AI 改完我都先 diff 再 commit,而不是干完一大堆再统一 commit,那时候改动太多,diff 已经没法看了。

* * *

## 8. 有没有用过 Superpower、OpenRouter 等第三方增强工具

### 核心答案

| 工具                         | 用途                     | 特点                     |
| -------------------------- | ---------------------- | ---------------------- |
| **OpenRouter**             | 统一 API 网关,一个 key 路由多模型 | 方便多模型对比,有用量统计          |
| **Superpower for ChatGPT** | 增强 ChatGPT 界面          | 历史搜索、prompt 模板、文件夹分类   |
| **Continue.dev**           | 开源 IDE 插件,自接多种模型       | 支持自定义 context provider |
| **Aider**                  | 命令行 AI 编程,支持 git 集成    | 自动 commit,适合纯终端工作流     |

关注核心:**context 精准控制**、**多模型路由**、**数据本地化**(不出境)。

* * *

### 🔧 Demo:OpenRouter 多模型对比同一个问题

// compare-models.ts ------ npx ts-node compare-models.ts const MODELS = 'anthropic/claude-sonnet-4-5', 'openai/gpt-4o', 'google/gemini-2.0-flash', ;

const QUESTION = '用 100 字以内解释什么是闭包,举一个实际应用场景';

async function ask(model: string, question: string) { const res = await fetch('openrouter.ai/api/v1/chat...', { method: 'POST', headers: { Authorization: Bearer ${process.env.OPENROUTER_API_KEY}, 'Content-Type': 'application/json', }, body: JSON.stringify({ model, messages: { role: 'user', content: question }, max_tokens: 200, }), }); const data = await res.json(); return data.choices0.message.content; }

(async () => { for (const model of MODELS) { console.log(\n=== ${model} ===); console.log(await ask(model, QUESTION)); } })();

markdown 复制代码
* * *

### 💬 深追 Q&A 模拟

**Q:你用 OpenRouter 会不会有数据安全问题,公司代码过第三方是否合规?**

> 这是实际工作中要考虑的。我的做法是:公司项目的核心业务代码不过第三方,只在个人项目和开源项目里用 OpenRouter 做多模型对比。公司内部用的是厂商直连 API(Anthropic / OpenAI 官方),如果有私有化需求就用本地部署的模型(Ollama + 开源模型)。这是个合规边界,面试时可以主动提出来,显示你有安全意识。

* * *

## 9. 为什么 AI 流式输出用 SSE,不用 WebSocket

### 核心答案

| 维度   | SSE                       | WebSocket     |
| ---- | ------------------------- | ------------- |
| 通信方向 | 单向(服务端 → 客户端)             | 双向            |
| 协议基础 | 标准 HTTP                   | 需要 Upgrade 握手 |
| 断线重连 | 浏览器自动重连                   | 需手动实现         |
| 负载均衡 | 天然支持                      | 需要代理额外配置      |
| 鉴权   | EventSource 不支持自定义 Header | 握手时可带 Header  |
| 实现成本 | 低(几行代码)                   | 高(需维护连接状态)    |

**AI 选 SSE:** 流式输出是单向推送,SSE 语义完全匹配;基于 HTTP 对基础设施友好;HTTP/2 下性能不输 WebSocket。

**用 WebSocket 的场景:** AI 对话需要实时打断(用户说话中途停止生成)、多人协作(多个用户同时发消息)。

* * *

### 🔧 Demo:SSE vs WebSocket 最小对比(可在浏览器 Console 运行)

// SSE 客户端(EventSource) const es = new EventSource('/sse-stream'); es.onmessage = e => console.log('SSE received:', e.data); es.onerror = () => console.log('SSE error, will auto-reconnect'); // 断线后浏览器自动重连,无需任何额外代码

// WebSocket 客户端 const ws = new WebSocket('ws://localhost:3001'); ws.onopen = () => console.log('WS connected'); ws.onmessage = e => console.log('WS received:', e.data); ws.onclose = () => { console.log('WS closed, manually reconnecting...'); setTimeout(() => new WebSocket('ws://localhost:3001'), 3000); // 手动重连 };

markdown 复制代码
* * *

### 💬 深追 Q&A 模拟

**Q:你说 HTTP/2 下 SSE 性能不输 WebSocket,能解释一下为什么吗?**

> HTTP/1.1 下,一个 SSE 连接占一个 TCP 连接,浏览器对同域名有 6 个并发连接限制,如果同时开多个 SSE 会被限制。HTTP/2 下,多个 SSE 流可以复用同一个 TCP 连接(多路复用),这个限制就消失了。WebSocket 本来的优势之一是不受 HTTP 连接限制,但 HTTP/2 + SSE 这个组合在性能上已经很接近了,而 SSE 在运维上更简单。

**Q:SSE 不能自定义请求头,鉴权是个问题,你实际怎么解决的?**

> 两种方案,生产里我用第一种:用 `fetch` + `ReadableStream` 替代 `EventSource`,这样可以带 `Authorization` header,同时也支持 POST 请求(EventSource 只能 GET)。第二种是 URL 里带 token 参数,但 token 会出现在服务器 access log 里,安全性差,不推荐。

* * *

## 10. SSE 流式输出的完整过程,每步数据怎么处理

### 核心答案

SSE 是基于 HTTP 的单向推送协议,每条消息纯文本格式,以空行结束。

**协议格式:**

data: {"token": "你好"}\n \n data: {"token": "世界"}\n \n data: DONE\n \n

markdown 复制代码
**数据处理链路:**

Uint8Array chunk → TextDecoder.decode(chunk, { stream: true }) ← 防中文截断 → 拼入 buffer → 按 \n 切行,最后不完整行留 buffer → 过滤非 data: 开头的行 → JSON.parse(payload) → 取 token 字段 → 追加到 UI

markdown 复制代码
* * *

### 🔧 Demo:最小可运行 SSE Server + 客户端

**服务端(Node.js,存为 `sse-server.js`):**

// sse-server.js ------ node sse-server.js const http = require('http');

http.createServer((req, res) => { if (req.url === '/stream') { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', });

ini 复制代码
const tokens = '你好世界,这是一段流式输出的文字。'.split('');
let i = 0;

const timer = setInterval(() => {
  if (i >= tokens.length) {
    res.write('data: [DONE]\n\n');
    clearInterval(timer);
    res.end();
    return;
  }
  res.write(`data: ${JSON.stringify({ token: tokens[i++] })}\n\n`);
}, 100);

req.on('close', () => clearInterval(timer));

} else { // 返回客户端 HTML res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(`
`); } }).listen(3000, () => console.log('Open http://localhost:3000')); ```

shell 复制代码
# 运行
node sse-server.js
# 打开浏览器访问 http://localhost:3000
# 可以看到文字逐字流式出现

💬 深追 Q&A 模拟

Q:TextDecoderstream: true 你能解释清楚为什么必须加吗?

UTF-8 是变长编码,中文字符占 3 个字节。当服务端的一个 chunk 恰好在这 3 个字节中间切断时,比如前 2 个字节在这个 chunk,第 3 个字节在下一个 chunk,如果不加 stream: true,TextDecoder 会在当前 chunk 结束时尝试把不完整的字节序列解码,输出乱码(通常是 <U+FFFD>)。加了 stream: true 后,TextDecoder 知道还有后续数据,会把不完整的字节缓存起来等下一个 chunk 到了再一起解码。

Q:buffer 里保留最后一行是什么逻辑,为什么不直接按行处理?

因为 response.body.getReader() 读出来的 chunk 是任意大小的字节片段,不保证按换行符对齐。一个 SSE 消息 data: {"token":"好"}\n\n 可能被切成两个 chunk:data: {"token":"好"}\n\n。如果直接处理每个 chunk,就会尝试解析一个不完整的行,JSON.parse 失败。所以要把 chunk 拼到 buffer 里,按 \n 切行,最后一段可能不完整的保留到下一个 chunk 拼完再处理。

Q:SSE 断线了,Last-Event-ID 续传是怎么工作的?

服务端在每条消息加 id: 42\n,浏览器的 EventSource 会记住最后收到的 ID。断线重连时,浏览器自动在请求头带 Last-Event-ID: 42,服务端收到后从 ID 42 之后的消息开始推送,不需要客户端做任何额外逻辑。用 fetch + ReadableStream 的话,这个机制要手动实现:记录最后的 event id,重连时在请求头里带上。


11. SSE 流式场景下,如何判断 Markdown 表格"完整"后再渲染

核心答案

核心策略:行级状态机 + pending 缓冲 + 惰性渲染

表格结束信号:收到第一个不以 | 开头的行(或流结束)。


🔧 Demo:最小可运行的流式 Markdown 渲染器

kotlin 复制代码
// streaming-md.ts  ------  npx ts-node streaming-md.ts
type Block = { type: 'text'; content: string } | { type: 'table'; lines: string[] };

class StreamingMarkdownParser {
  private buffer: string[] = [];
  private inTable = false;
  private pending = '';
  private blocks: Block[] = [];

  feed(chunk: string) {
    const lines = (this.pending + chunk).split('\n');
    this.pending = lines.pop()!;
    for (const line of lines) this.processLine(line);
  }

  private processLine(line: string) {
    if (/^\s*|/.test(line)) {
      this.inTable = true;
      this.buffer.push(line);
    } else {
      if (this.inTable) this.flushTable();
      if (line.trim()) this.blocks.push({ type: 'text', content: line });
    }
  }

  private flushTable() {
    const hasSep = this.buffer.some(l => /^|\s*[-:|]+\s*|/.test(l));
    if (hasSep) {
      this.blocks.push({ type: 'table', lines: [...this.buffer] });
    } else {
      // 不合法的表格结构,降级为普通文本
      this.blocks.push(...this.buffer.map(l => ({ type: 'text' as const, content: l })));
    }
    this.buffer = [];
    this.inTable = false;
  }

  finish(): Block[] {
    if (this.pending) this.processLine(this.pending);
    if (this.inTable) this.flushTable();
    return this.blocks;
  }
}

// 模拟 SSE 流式输入(每次 feed 一个 token)
const parser = new StreamingMarkdownParser();
const fakeStream = [
  '普通文本\n',
  '| 列A | 列B |\n',
  '| --- | --- |\n',
  '| 数据1 | 数',  // ← 在行中间截断
  '据2 |\n',
  '\n',           // ← 触发表格结束
  '更多文本\n',
];

for (const chunk of fakeStream) {
  parser.feed(chunk);
}

const blocks = parser.finish();
console.log(JSON.stringify(blocks, null, 2));
// 输出:[
//   { type: 'text', content: '普通文本' },
//   { type: 'table', lines: ['| 列A | 列B |', '| --- | --- |', '| 数据1 | 数据2 |'] },
//   { type: 'text', content: '更多文本' }
// ]

💬 深追 Q&A 模拟

Q:如果 AI 的最后一个输出就是表格,没有后续文本,你的状态机会怎样?

这是个经典的 off-by-one 问题。流结束时 inTable 还是 true,buffer 里有数据但没有收到触发 flushTable 的非表格行。所以必须在流结束时强制调 finish(),里面做最终的 flushTable。EventSource 的 close 事件、fetch stream 的 done === true,都要触发这个 finish(),否则最后一个表格会永远留在 buffer 里不渲染。

Q:代码块(```````````包裹的内容)里如果有 | 字符,会不会被误判为表格行?

会!这是这个简化实现的缺陷。完整实现需要先判断当前是否在代码块内(tracking inCodeBlock 状态,遇到 ``````````` 切换),在代码块内的行跳过表格检测,直接缓冲。实际项目里我不会手写这些,会用 markedmarkdown-it 的流式解析接口,它们已经处理了这些边界情况。


12. 前端 AI 开发中使用 SDD(Spec-Driven Development)有什么经验

核心答案

SDD = 先写规格(Spec),再让 AI 按规格实施。

核心流程:

复制代码
需求理解 → 写 Spec(功能 + 边界 + 接口约定 + 不做什么)
  → Review Spec → AI 按 Spec 实施 → 对照 Spec 验收

关键经验: Spec 里"不做什么"比"做什么"更重要;Spec 即验收标准;Spec 是多 Agent 的上下文对齐工具。

一句话: SDD 把"和 AI 的沟通成本"转化成"写规格的成本",确定性需求划算,模糊探索不一定。


🔧 Demo:一个完整的 Spec 文档示例

php 复制代码
<!-- specs/search-box.md -->

# SearchBox 组件 Spec

## 功能描述
一个带防抖的搜索输入框,用户输入后触发搜索回调。

## 接口约定
```ts
interface SearchBoxProps {
  onSearch: (keyword: string) => void;  // 搜索触发回调
  placeholder?: string;                  // 默认:"搜索..."
  debounceMs?: number;                   // 默认:300
  disabled?: boolean;
}

行为规格

  • 用户停止输入 300ms(可配置)后触发 onSearch
  • keyword 为空字符串时不触发 onSearch(清空不搜索)
  • 有内容时显示清空按钮(×),点击清空输入框并调用 onSearch('')
  • disabled=true 时输入框灰色,不可交互,不触发回调
  • 组件卸载时取消防抖定时器(防内存泄漏)

不做的事(Out of Scope)

  • ❌ 不做搜索结果的展示(那是 SearchResults 组件的职责)
  • ❌ 不做搜索历史
  • ❌ 不处理 IME 输入法组合输入(中文拼音输入中不触发)

文件范围

只修改以下文件,不要改其他任何文件:

  • src/components/SearchBox/index.tsx(新建)
  • src/components/SearchBox/index.module.css(新建)
  • src/components/SearchBox/index.test.tsx(新建)

验收标准

对照上方行为规格逐条核对,所有 checkbox 通过即为完成。

复制代码

使用方式:把 spec 传给 Claude Code

claude "请按照 specs/search-box.md 的规格实现 SearchBox 组件, 实现完成后对照规格里的 checkbox 逐条自检"

markdown 复制代码
* * *

### 💬 深追 Q&A 模拟

**Q:Spec 写完 AI 实施,实施完发现 Spec 本身有问题,怎么办?**

> 这是 SDD 里最常见的问题。发现 Spec 有问题要先改 Spec,再让 AI 按新 Spec 重新实施,不要直接跟 AI 说"刚才那个需求变了"然后继续在同一个 session 里改,那样 Spec 和代码会越来越不对齐。改 Spec 的成本是值得的,因为 Spec 是后续验收和后续需求变更的基准,不维护它的代价更高。

**Q:需求比较模糊,写不出清楚的 Spec,还适合 SDD 吗?**

> 不适合。需求模糊时适合先做 spike(技术探索),跑一个 POC 搞清楚技术方向,这个阶段用"让 AI 先给我一个能跑的版本看看效果"的方式更高效。POC 做完、方向确定了,再写 Spec 进入 SDD 流程。SDD 适合的是"我知道要做什么,但实施起来繁琐"的场景,不适合"我不知道要做什么"的探索阶段。

**Q:团队里有人不愿意写 Spec,说写 Spec 的时间不如直接让 AI 做,你怎么说服他?**

> 我会先认可他的观点------需求简单时确实如此,不用为了 SDD 而 SDD。但如果一个功能要来回改 3 次,每次改完 AI 理解偏差,那 3 次的成本早就超过写一次 Spec 的成本了。更重要的是,Spec 不只是给 AI 看的,它也是和产品、设计、后端对齐的工具。如果写完 Spec 大家都没有异议,说明理解是对齐的;如果写完发现有分歧,早发现早解决,比代码写完再返工要便宜得多。

* * *

* * *

# Coding Agent 项目深追问题

* * *

## 13. Claude Code 和 Codex 各自有什么特别之处

**答:**

| 维度     | Claude Code                   | Codex(OpenAI)           |
| ------ | ----------------------------- | ----------------------- |
| 接入方式   | CLI 工具,终端直接用                  | API / GitHub Copilot 后端 |
| 上下文管理  | CLAUDE.md 自动注入、hooks、skill 系统 | 主要靠 system prompt       |
| 指令跟随   | 精准,明确约束时很少越界                  | 灵活,有时会"顺手"扩展            |
| 代码审查深度 | 执行为主,方案实施快                    | 推理深度高,批判性更强             |
| 工具生态   | MCP 协议,可扩展工具                  | Function Calling,生态更广   |
| 多文件操作  | 原生支持,能跨文件追踪依赖                 | 需要手动控制文件范围              |
| 适合场景   | 主力实施、复杂多文件任务                  | 独立 Review、批判性分析         |

**核心差异:** Claude Code 是"执行型",快但思考浅;Codex 是"审查型",慢但深。两者组合------Claude Code 做方案,Codex 审查------形成制衡。

### 💬 深追 Q&A

**Q:你说 Codex 推理深度更高,能举个具体例子体现这个差异吗?**

> 有一次让两个模型都 review 同一个并发控制的代码。Claude Code 给的 review 主要是风格和命名问题。Codex 发现了一个 race condition:两个异步操作都读了同一个状态变量,在特定时序下会出现数据不一致。这种需要跨多个执行路径推理的问题,Codex 更容易抓到,因为它更倾向于深度分析而不是快速响应。

* * *

## 14. 输入到模型的 Prompt 由哪些部分组成,哪些必须注入,哪些不是

**答:**

一个完整的 LLM prompt 通常由以下层次组成:

┌─────────────────────────────────────────┐ │ System Prompt(系统层) │ ← 必须,定义角色/能力/约束 ├─────────────────────────────────────────┤ │ Long-term Memory(长期记忆) │ ← 按需,用户偏好/项目规范 ├─────────────────────────────────────────┤ │ Skill / Tool Definitions(工具定义) │ ← 按需,当前任务需要的工具 ├─────────────────────────────────────────┤ │ Conversation History(对话历史) │ ← 按需,可压缩 ├─────────────────────────────────────────┤ │ Retrieved Context(RAG 召回内容) │ ← 按需,相关文档片段 ├─────────────────────────────────────────┤ │ User Message(用户当前输入) │ ← 必须 └─────────────────────────────────────────┘

markdown 复制代码
**必须注入:**

-   **System Prompt**:定义模型行为边界,没有它模型容易漂移
-   **User Message**:当前任务输入,没有就无从响应

**按需注入(动态决策):**

-   **长期记忆**:只在记忆与当前任务相关时注入,避免无关信息干扰
-   **工具定义**:当前任务需要哪些工具就注入哪些,不全量注入(减少 token 消耗,降低幻觉)
-   **对话历史**:可压缩,超出阈值时摘要替换原始对话
-   **RAG 召回**:根据当前 query 相似度检索,不全量注入知识库

**设计原则:** 最小必要上下文(Minimum Necessary Context),注入越精准,输出质量越高,token 成本越低。

### 💬 深追 Q&A

**Q:工具定义为什么不全量注入,只注入当前需要的?**

> 两个原因。第一是 token 成本,一个工具定义可能有几百 token,十几个工具就是几千 token,每次调用都浪费。第二是模型选择工具时的"注意力"问题------工具越多,模型选错工具的概率越高,叫做 tool selection confusion。只注入当前任务真正需要的工具,模型能更准确地选择。

**Q:长期记忆按需注入,你怎么判断"按需",用什么方法决定要不要注入?**

> 语义相似度检索。把长期记忆按条目向量化存储,每次新任务来时,把用户消息做 embedding,和记忆库做 cosine similarity 检索,超过阈值(比如 0.75)的记忆条目才注入。这样既避免全量注入,又能在相关时自动召回。

* * *

## 15. 你做的 Coding Agent 和 Claude Code / Codex 的区别在哪

**答:** (根据自身项目情况替换,以下是参考框架)

| 维度         | Claude Code / Codex  | 你的 Coding Agent      |
| ---------- | -------------------- | -------------------- |
| 定制化程度      | 通用,需要靠 prompt 定制     | 针对特定场景/团队规范深度定制      |
| 记忆系统       | 基本无持久化记忆             | 长短期记忆分层,跨会话持久化       |
| Skill 体系   | 靠 slash command / 约定 | 自定义 skill 分层,自动匹配    |
| 上下文管理      | CLAUDE.md 等静态注入      | 动态 prompt,按任务类型组装    |
| 多 Agent 编排 | 单 Agent 为主           | 多 Agent 流水线,任务分发     |
| 成本控制       | 无精细控制                | token budget 管理,压缩策略 |
| 知识集成       | 靠文件上下文               | RAG 集成,知识库动态召回       |

**你的 Agent 的核心价值:** 深度集成团队/个人工作流,有持久化的上下文和记忆,能跨会话保持一致性,Claude Code 每次对话是全新的,不记得上次做了什么。

### 💬 深追 Q&A

**Q:你自己做的 Agent 和直接用 Claude Code 相比,日常使用哪个更多?**

> 实际上两个都在用,场景不同。Claude Code 我用来做临时性的、一次性的任务,比如快速重构一个函数、生成一个测试文件;我自己的 Agent 用来做需要跨会话积累上下文的长期任务,比如一个长达两周的功能开发,它记得之前的决策和规范,不用每次重新介绍项目背景。

* * *

## 16. 上下文压缩怎么做的,三层压缩策略是什么

**答:**

上下文压缩的目标是在有限的 token 窗口内保留最关键的信息。三层策略:

**第一层:消息级别裁剪(轻量)**

-   触发条件:历史消息条数超过阈值(如 20 条)
-   做法:滑动窗口,只保留最近 N 条对话,丢弃最早的
-   保留:System prompt 永远不裁剪;最近的 user-assistant 对

**第二层:摘要压缩(中量)**

-   触发条件:总 token 超过上下文窗口的 60%
-   做法:调用模型将前半段对话压缩为摘要("前面我们做了 X,决定了 Y,当前状态是 Z")
-   摘要以 system message 形式注入,替换原始对话

**第三层:关键事实提取(重量)**

-   触发条件:长期会话(超过 N 小时或 M 轮对话)
-   做法:提取关键决策、代码变更、约定事项存入长期记忆,完全清空短期对话历史
-   下次对话从长期记忆重建上下文

Token 使用率: ░░░░░░░░░░ 0% ────────── 60% → 触发第二层摘要 ──────────── 80% → 触发第三层提取 ─────────────── 100% → 截断(最差情况)

markdown 复制代码
### 💬 深追 Q&A

**Q:为什么是 60% 这个阈值,不是 80% 或者满了再压缩?**

> 两个原因。第一,压缩本身需要消耗 token(要调用模型生成摘要),如果等到 90% 再压缩,压缩后剩余空间可能不够继续对话。第二,摘要质量和剩余 context 量有关,context 越满,模型处理时注意力越分散,摘要质量越差。60% 是经验值,留出足够的"呼吸空间"给压缩操作本身。

**Q:摘要是用同一个主模型生成,还是用轻量模型?**

> 摘要生成用轻量模型(如 Claude Haiku 或 GPT-4o-mini),原因是:① 摘要任务不需要强推理能力,轻量模型够用 ② 成本差异很大,Haiku 比 Sonnet 便宜 10 倍以上 ③ 速度快,不阻塞主流程。主模型只做核心任务,摘要是辅助操作。

* * *

## 17. 压缩过度导致效果不理想,怎么发现,怎么处理

**答:**

**如何发现压缩过度:**

1.  **输出质量监控**:维护一个质量评分机制,比较压缩前后相同任务的输出质量(可以用另一个模型做评判)
1.  **用户反馈信号**:用户频繁说"你刚才说的......你忘了"、"这个之前已经决定了"------说明关键信息被压掉了
1.  **上下文一致性检查**:每轮对话后让模型输出"当前任务状态摘要",和上一轮比较是否有信息丢失
1.  **明显错误**:模型开始重复已完成的工作,或者忽略已知约束

**处理策略:**

发现压缩过度 ├── 短期:从长期记忆恢复关键事实,重新注入 ├── 中期:降低压缩激进程度(提高触发阈值,增加保留信息量) └── 长期:改善关键事实提取算法,确保重要决策不被裁剪

复制代码

// 压缩质量检查(伪代码) async function checkCompressionQuality( originalCtx: string, compressedCtx: string ): Promise { const score = await model.evaluate(` 原始上下文: originalCtx压缩后: {originalCtx} 压缩后: originalCtx压缩后:{compressedCtx}

markdown 复制代码
评估压缩质量(0-10),重点检查:
1. 关键决策是否保留
2. 重要约束是否保留  
3. 当前任务状态是否准确

只输出分数

`); return parseFloat(score); }

markdown 复制代码
* * *

## 18. Agent 对原有任务进一步修改(新加功能),系统怎么做

**答:**

这本质是"会话恢复 + 增量注入"的问题。

**流程:**

用户发起新修改请求 ↓ 从长期记忆检索相关上下文 (上次的任务描述、已完成内容、当前代码状态) ↓ 重建 prompt:

  • System prompt(不变)
  • 长期记忆摘要(已完成 X,当前状态 Y)
  • 相关代码文件(当前版本,非历史版本)
  • 新的修改请求(明确说明是在现有基础上修改) ↓ 执行,完成后更新长期记忆
markdown 复制代码
**关键:需要重新注入的信息**

-   ✅ 已完成功能的摘要(让模型知道不用重做)
-   ✅ 当前代码文件(最新版本,不是历史对话里的版本)
-   ✅ 已有的约束和规范(防止新功能违反旧约定)
-   ❌ 不需要注入:历史的调试过程、已解决的错误、被废弃的方案

### 💬 深追 Q&A

**Q:新功能和旧功能有依赖关系,比如要改旧功能的接口来支持新功能,系统怎么处理?**

> 这是最复杂的情况。系统需要在 prompt 里明确说明变更点:"现有接口 X 需要从 A 改成 B,以下是受影响的调用方:[列表],请一并修改。"关键是要用工具扫描整个代码库找到所有调用方,而不是靠模型记忆------模型对代码的记忆是不可靠的,必须实时读文件。这也是为什么 Agent 要有文件系统访问工具,而不只是对话。

* * *

## 19. 工具调用的流程,skill 能替代工具吗

**答:**

**工具调用流程(Function Calling):**

用户输入 ↓ 模型生成响应(包含 tool_use 块): { "name": "read_file", "input": { "path": "src/api.ts" } } ↓ 系统执行工具,拿到结果 ↓ 把工具结果作为 tool_result 注入下一轮 ↓ 模型基于结果继续生成(可能再次调用工具) ↓ 模型输出最终文本响应(无 tool_use,循环结束)

markdown 复制代码
**Skill 能替代工具吗?** 不能完全替代,两者本质不同:

| 维度   | 工具(Tool)           | Skill                  |
| ---- | ------------------ | ---------------------- |
| 本质   | 代码执行,有实际副作用        | Prompt 模板,纯文本注入        |
| 能力   | 读文件、写文件、调 API、运行命令 | 提供任务指导、规范约束、思维框架       |
| 副作用  | 有(改变文件系统、调用外部服务)   | 无(只影响模型的思考方向)          |
| 替代关系 | 工具能做 skill 做不到的事   | Skill 能减少重复的 prompt 编写 |

**结论:** Skill 是工具的补充,不是替代。需要和外部系统交互的能力(读写文件、执行命令、调用 API)必须是工具;而高频的 prompt 模式、规范注入可以用 skill 封装。

* * *

## 20. 面向复杂任务,Coding Agent 的 Plan 怎么做

**答:**

复杂任务 Planning 的核心是把"一个大问题"拆成"多个可执行的子任务",并确定依赖关系。

**Planning 流程:**

接收复杂任务 ↓ Phase 1 - 任务理解(Clarification) 让模型列出不清楚的地方,用户确认后再规划 ↓ Phase 2 - 任务分解(Decomposition) 输出结构化 Plan: { "tasks": { "id": "T1", "description": "...", "files": \[..., "deps": \[\] }, { "id": "T2", "description": "...", "files": ..., "deps": "T1" }, { "id": "T3", "description": "...", "files": ..., "deps": \[\] }, // 可与 T2 并行 ] } ↓ Phase 3 - 执行编排(Orchestration) 按依赖关系并行/串行执行,无依赖的任务并行跑 ↓ Phase 4 - 集成验证(Integration) 子任务完成后,整体 review 接口一致性

markdown 复制代码
### 💬 深追 Q&A

**Q:Plan 里的依赖关系怎么判断,模型能准确识别吗?**

> 模型判断依赖关系有时会漏,特别是隐式依赖(比如两个任务都要修改同一个共享类型定义文件)。我的做法是在模型生成 Plan 后,再用工具扫描每个子任务涉及的文件列表,自动检查文件重叠,重叠的任务标记为有依赖,不能并行。工具检查比模型判断更可靠。

* * *

## 21. 多 Agent 编排具体怎么做,每个子 Agent 的区别,为什么这么设计

**答:**

**编排架构:**

Orchestrator Agent(父) ├── 接收用户请求 ├── 生成 Plan,分发子任务 ├── 汇总子 Agent 结果 └── 处理冲突和集成

Worker Agents(子,按角色分工) ├── Implementer Agent ------ 写代码,只关注实现 ├── Reviewer Agent ------ 审查代码,批判性视角 ├── Tester Agent ------ 写测试,关注覆盖率和边界 └── Documenter Agent ------ 生成文档,关注可读性

markdown 复制代码
**每个子 Agent 的区别:**

-   **System prompt 不同**:Reviewer 的 system prompt 强调"批判性、找问题",Implementer 强调"精准执行、符合规范"
-   **工具集不同**:Implementer 有写文件权限,Reviewer 只有读权限(防止 review 时顺手改代码)
-   **上下文不同**:只给当前子任务相关的文件,不共享其他子 Agent 的工作过程

**为什么不让所有子 Agent 共享工具?**

> 权限最小化原则。Reviewer 不需要写文件,给了写权限反而引入风险------它可能在 review 时直接修改,绕过了 Implementer 的工作流。工具权限对应职责范围,职责越小、权限越小、出错面越小。

* * *

# Coding Agent 项目细节问题

* * *

## 22. Coding Agent 整个链路的运转流程

**答:** (参考框架,根据实际项目替换)

用户输入(自然语言任务描述) ↓ ① 意图理解 + Skill 匹配 ------ embedding 检索相关 skill,注入对应 prompt 规范 ↓ ② 长期记忆召回 ------ 向量检索,找和当前任务相关的历史记忆注入上下文 ↓ ③ 动态 Prompt 组装 ------ System prompt + 长期记忆 + skill + 对话历史(压缩后)+ 当前任务 ↓ ④ 模型推理(Plan 阶段) ------ 输出结构化执行计划(子任务列表 + 依赖关系) ↓ ⑤ 工具执行循环(ReAct 模式) ------ 读文件 → 分析 → 写文件 → 验证 → 循环直到完成 ↓ ⑥ 子任务完成,汇报结果 ↓ ⑦ 更新长期记忆(提取关键决策、变更摘要) ↓ ⑧ 返回用户,等待下一轮输入

markdown 复制代码
* * *

## 23. Skill 分层体系怎么设计,为什么这么设计

**答:**

三层结构:

Layer 3 ------ 领域 Skill(最具体) 例:react-component、api-design、test-writing 触发:特定任务类型

Layer 2 ------ 规范 Skill(中间层) 例:code-style、commit-convention、pr-format 触发:每次写代码/提交时自动注入

Layer 1 ------ 基础 Skill(最通用) 例:project-context、team-preference 触发:每次对话都注入

markdown 复制代码
**为什么分层:**

-   避免无效注入:不是每个任务都需要所有 skill,按需注入节省 token
-   灵活组合:一个复杂任务可以同时触发多层 skill(基础 + 规范 + 领域)
-   独立维护:各层 skill 可以独立更新,不互相影响

* * *

## 24. 用户输入怎么和相关 Skill 匹配

**答:**

语义匹配流程:

用户输入 ↓ 文本 embedding(向量化) ↓ 和 skill registry 里每个 skill 的描述向量做 cosine similarity ↓ 超过阈值(如 0.7)的 skill 候选列表 ↓ 按分数排序,取 Top K(如 Top 3) ↓ 注入匹配到的 skill 内容

markdown 复制代码
**辅助策略:**

-   关键词触发:`/review` 命令直接触发 review skill,不走语义匹配
-   强制注入:基础层 skill 不走匹配,每次都注入
-   上下文感知:当前对话里已经触发过的 skill,后续轮次维持注入(避免反复检索)

* * *

## 25. Skill 沉淀机制

**答:**

Skill 不只是手动创建,还可以从对话中提炼沉淀:

**自动沉淀触发条件:**

-   用户对某个 AI 输出明确表示"很好,以后都这样做"
-   同一类 prompt 被重复使用超过 N 次
-   用户明确说"把这个存成 skill"

**沉淀流程:**

识别沉淀时机 ↓ 提取高频/高质量 prompt 模式 ↓ 让模型自动生成 skill 文档(name/description/content) ↓ 用户确认(or 自动保存) ↓ 写入 skill 文件,热更新到 registry

markdown 复制代码
这样 skill 库是随使用不断增长的,而不是一次性手工配置。

* * *

## 26. 长短期记忆怎么设计,静态 vs 动态长期记忆

**答:**

**记忆分层:**

短期记忆(Short-term) ------ 当前对话的消息历史 ------ 存在内存,会话结束后消失 ------ 超出 token 阈值时触发压缩

长期记忆(Long-term) ------ 跨会话持久化,存在向量数据库 ------ 分为静态和动态两种

markdown 复制代码
**静态长期记忆 vs 动态长期记忆:**

| 维度   | 静态长期记忆                | 动态长期记忆                                     |
| ---- | --------------------- | ------------------------------------------ |
| 内容   | 用户偏好、项目规范、固定约定        | 每次对话后提取的新事实                                |
| 更新频率 | 低,手动维护                | 高,每轮对话自动更新                                 |
| 例子   | "用 TypeScript,不用 any" | "用户在 2026-06-01 决定把状态管理从 Redux 换成 Zustand" |
| 作用   | 提供稳定的背景规范             | 记录动态的决策和进展                                 |

**为什么要区分两种:**

静态的东西如果每次对话都更新,会引入噪声(AI 可能会"覆盖"掉用户设定的规范)。动态的东西如果不自动积累,用户每次都要重新介绍背景。两种分开管理,各自有不同的写入策略和召回逻辑。

* * *

## 27. 长期记忆快速积累过多,怎么处理

**答:**

**预防策略:**

-   设置记忆条目上限(如 1000 条),超出时触发整理
-   去重:相似度 > 0.9 的记忆条目合并
-   设置记忆有效期(TTL),超过 N 天未被召回的记忆降低权重或归档

**定期整理(Memory Consolidation):**

触发:记忆条目超过阈值 or 定时(每周) ↓ 把所有记忆条目聚类(语义聚类) ↓ 同一簇内的记忆合并为一条摘要记忆 ↓ 原始记忆条目降级为"归档",不参与日常召回 ↓ 只保留摘要记忆作为活跃记忆

markdown 复制代码
这类似于人类记忆的"固化"过程------细节遗忘,关键事实保留。

* * *

## 28. 大模型怎么决定长期记忆是否召回

**答:**

两阶段:

**阶段一:向量检索(粗召回)**

把用户当前输入做 embedding,和记忆库做 cosine similarity 检索,返回 Top-K 最相关的记忆候选。

**阶段二:模型重排(精召回)**

把候选记忆和当前 query 一起发给模型,让模型判断哪些记忆真正有用:

以下是关于当前任务可能相关的记忆(按相似度排序): 记忆列表

请判断哪些记忆对当前任务"用户想实现搜索功能"有帮助, 输出相关记忆的 ID 列表,不相关的忽略。

markdown 复制代码
**为什么需要两阶段:** 纯向量检索基于语义相似度,有时会召回"看起来相似但实际无关"的记忆。让模型做最终判断,结合语义理解决定真正的相关性,准确率更高。

* * *

## 29. 动态 Prompt vs 静态 Prompt

**答:**

**静态 Prompt:**

-   内容固定,每次对话都一样
-   例子:System Prompt 里的角色定义、基础规范
-   特点:稳定、可预测、不受运行时状态影响

**动态 Prompt:**

-   内容在运行时根据上下文动态组装
-   组成部分按条件注入/不注入
-   例子:

function buildPrompt(context: Context): string { const parts = SYSTEM_BASE, // 静态,永远注入 context.longTermMemory.join('\\n'), // 动态,按相关性注入 context.relevantSkills.map(s =\> s.content).join('\\n'), // 动态,按任务匹配 compressHistory(context.messages), // 动态,超阈值时压缩 context.ragResults ?? '', // 动态,有检索结果才注入 `用户当前任务:${context.userInput}`, // 动态,每次不同 ; return parts.filter(Boolean).join('\n\n'); }

markdown 复制代码
**设计原则:** 静态部分保持稳定,动态部分最小化注入(只注入当前任务真正需要的信息),两者分开管理便于调试和迭代。

* * *

## 30. 模型底座选型,Token 消耗和成本

**答:** (参考框架,根据实际情况填数字)

**模型选型考虑:**

| 任务           | 推荐模型                       | 理由          |
| ------------ | -------------------------- | ----------- |
| 主力实施(复杂代码任务) | Claude Sonnet / GPT-4o     | 推理强,指令跟随好   |
| 摘要/压缩(辅助任务)  | Claude Haiku / GPT-4o-mini | 成本低 10 倍,够用 |
| Embedding    | text-embedding-3-small     | 向量质量好,成本低   |
| 轻量判断(路由/分类)  | GPT-4o-mini / Haiku        | 快速、便宜       |

**Token 消耗估算:**

写 1000 行代码的任务:

-   输入 token(prompt + 上下文 + 工具结果):约 50k--100k token
-   输出 token(代码 + 解释):约 5k--20k token
-   总计:约 70k--120k token/任务

**成本估算(以 Claude Sonnet 为例):**

-   输入:$3/1M token
-   输出:$15/1M token
-   一个复杂任务:约 $0.3--0.8

**为什么成本这么高:** 主要是上下文注入------每轮对话都要带完整的 skill、记忆、历史,这些背景信息的 token 累加起来远超代码本身。优化方向是精细化上下文管理,只注入真正需要的部分。

* * *

# RAG 项目深追问题

* * *

## 31. RAG 的数据来源是什么

**答:** (根据实际项目替换,以下是参考框架)

数据来源通常分几类:

| 来源类型            | 处理方式                 | 注意点                 |
| --------------- | -------------------- | ------------------- |
| 内部文档(PDF/Word)  | 解析 → 分块 → 向量化        | 格式复杂,需要处理表格/图片      |
| 网页 / Confluence | 爬取 → HTML 清洗 → 分块    | 去除导航、广告、重复内容        |
| 代码库             | AST 解析或文本分块          | 按函数/类切分比按行数切分好      |
| 数据库结构化数据        | 转为自然语言描述再向量化         | 需要 schema 感知的描述生成   |
| API 文档          | OpenAPI / Swagger 解析 | 按 endpoint 切分,带参数说明 |

**数据质量是 RAG 效果的上限**,垃圾进垃圾出,入库前的清洗比模型选型更重要。

* * *

## 32. 个人为什么要做 GraphRAG 项目

**答:** (参考思路)

可以从"发现了什么问题 → 现有方案为什么不够 → GraphRAG 怎么解决"来组织:

> 我在使用普通 RAG 系统时,发现对于需要跨文档推理的问题,回答质量很差。比如"某个技术决策的背景和影响"这类问题,涉及多个文档的关联信息,单纯向量检索找不到全图,回答是片面的。纯向量 RAG 擅长"找到相关段落",但不擅长"理解段落之间的关系"。GraphRAG 通过构建知识图谱,让检索有了图结构的多跳能力,能回答"A 和 B 有什么关系"这类需要推理的问题。这是我做这个项目的核心出发点。

* * *

## 22. GraphRAG 和纯向量检索的区别,解决了什么问题

**答:**

**纯向量检索的局限:**

-   每个文档 chunk 独立向量化,检索时基于语义相似度
-   擅长"这段话在哪里"(单跳检索)
-   无法处理需要跨多个文档/段落推理的问题(多跳推理)

**GraphRAG 的做法:**

-   在向量检索基础上,构建知识图谱:实体(Entity)、关系(Relation)、社区(Community)
-   检索时不只找相似 chunk,还沿知识图谱的边做多跳遍历

**解决的问题:**

| 问题类型              | 纯向量 | GraphRAG |
| ----------------- | --- | -------- |
| 直接问答(某概念是什么)      | ✅   | ✅        |
| 多跳推理(A 和 B 有什么关系) | ❌   | ✅        |
| 全局概括(整个文档库的主题)    | ❌   | ✅(社区摘要)  |
| 推理型问题(根据多个事实推结论)  | ❌   | ✅        |

**什么算推理型问题(举例):**

知识库里有:

  • 文档A:张三是部门A的负责人
  • 文档B:部门A负责项目X
  • 文档C:项目X使用了技术Y

推理型问题:张三对技术Y的熟悉程度如何影响项目X? → 需要跨 A/B/C 三个文档推理,纯向量检索很难做到

markdown 复制代码
### 💬 深追 Q&A

**Q:GraphRAG 的知识图谱构建成本很高,你怎么控制这个成本?**

> 两个策略。第一,不是所有内容都建图,只对核心知识(实体丰富、关系复杂的文档)建图,普通问答还是走向量检索。第二,图谱构建是离线异步的,入库时在后台跑,不影响实时响应。实体和关系抽取用轻量模型(Haiku/mini),只有社区摘要生成用强模型,控制成本。

**Q:Graph 里的关系是怎么抽取的,准确率能到多少?**

> LLM 做关系抽取,给定实体对和上下文,让模型判断关系类型和方向。准确率大概在 80-85% 左右,有噪声。关键是要有去噪机制:同一关系出现多次(多个文档都提到)才确认入图,出现一次的可能是误抽取,不入图或者降低置信度。

* * *

## 23. PDF 中各种形式的内容怎么处理

**答:**

PDF 的内容形式:

| 内容类型        | 处理方式                                               |
| ----------- | -------------------------------------------------- |
| 纯文本         | `pdfplumber` / `pymupdf` 直接提取                      |
| 表格          | `pdfplumber` 提取表格结构,转为 Markdown / CSV              |
| 图片          | 用视觉模型(GPT-4o / Claude)做图片描述(Image Caption)         |
| 公式          | MathPix API 或 `nougat`(PDF 学术公式专用模型)转 LaTeX        |
| 扫描件(图片 PDF) | OCR(Tesseract / PaddleOCR / Azure Form Recognizer) |
| 混合排版(多栏)    | `pdfplumber` 按坐标排序,还原阅读顺序                          |

**处理流程:**

PDF 输入 → 判断是否是扫描件(有无可提取文字层) → 普通 PDF:pymupdf 提取文字 + 表格 → 扫描件:OCR 转文字 → 检测图片块 → 视觉模型生成描述 → 检测表格 → 转结构化格式 → 分块(chunk)→ 向量化 → 入库

markdown 复制代码
* * *

## 24. 数据冗余和无关信息的入库前处理

**答:**

**清洗策略(Pipeline):**

1.  **去重**:文档级 MD5 hash 去重,避免同一文档入库多次;chunk 级语义去重(cosine similarity > 0.95 的 chunk 只保留一个)
1.  **噪声过滤**:移除页眉页脚(通常在固定坐标)、水印文字、导航菜单文本
1.  **质量过滤**:过短的 chunk(< 50 字)、乱码检测(非法字符比例 > X%)直接丢弃
1.  **相关性过滤**:用轻量分类模型判断 chunk 是否属于目标知识域,无关的不入库
1.  **结构保留**:保留标题层级(H1/H2/H3),chunk 时带上标题作为上下文前缀

* * *

## 25. 知识抽取怎么做

**答:**

知识抽取 = 从非结构化文本里提取结构化知识(实体、关系、属性)。

**流程:**

文档 chunk → 命名实体识别(NER):识别人名/地名/组织/产品/概念 → 关系抽取(RE):判断实体间的关系类型和方向 → 属性抽取:提取实体的属性值("张三 的 职位 是 CTO") → 知识融合:同一实体的不同表述合并("AI" = "人工智能") → 写入知识图谱

markdown 复制代码
**实现方式:**

-   小规模:LLM 直接做(prompt 里指定抽取格式,输出 JSON)
-   大规模:先用轻量 NER 模型识别实体,再用 LLM 做关系判断(降低成本)

* * *

## 26. 增量更新怎么做

**答:**

**核心问题:** 新文档入库不能触发全量重新索引,否则成本爆炸。

**策略:**

新文档到来 ↓ Hash 比对:新文档 vs 已入库文档 ├── 完全相同 → 跳过 ├── 新文档(无记录)→ 正常处理流程入库 └── 已有文档更新 → 差量更新: 只对变更的段落重新向量化 删除旧版 chunk,插入新版 chunk 更新知识图谱中受影响的节点和边

markdown 复制代码
**实现要点:**

-   每个 chunk 记录来源文档 ID + 文档版本号
-   删除某文档时,按文档 ID 批量删除所有 chunk
-   知识图谱里的节点带 source_doc_id,更新文档时同步更新相关节点

* * *

## 27. 文档冲突处理,版本控制,错误文档回滚

**答:**

**文档冲突处理:**

检测到冲突(同一事实在不同文档有不同描述) ↓ 记录冲突,不自动解决 ↓ 检索时:

  • 把冲突信息一并返回给模型
  • 让模型在回答时注明"来源 A 说 X,来源 B 说 Y,存在冲突"
  • 或按文档权重/时间戳优先(新文档 > 旧文档)
markdown 复制代码
**版本控制:**

-   每个文档入库时记录:文档 ID、版本号、入库时间、操作人
-   保留最近 N 个版本的快照(向量 + 原文)
-   新版本入库不立即删除旧版本,有验证期(如 24 小时)后再清理

**错误文档回滚:**

概念性命令

knowledge-base rollback

--doc-id "doc_xxx"

--to-version "v2" # 回滚到指定版本

底层操作:

1. 删除当前版本的所有 chunk(按 doc_id + version)

2. 恢复旧版本的 chunk 到向量数据库

3. 更新知识图谱(删除新版节点,恢复旧版节点)

4. 记录回滚操作日志

markdown 复制代码
* * *

# Fine-tuning 项目问题

* * *

## 28. 为什么要做 Fine-tuning 项目,出发点是什么

**答:** (参考框架,根据实际项目替换)

可以从"API 调用的局限 → Fine-tuning 能解决什么 → 个人学习价值"三层来回答:

> 出发点有两个。第一是工程层面:直接调用闭源 API 有数据隐私问题(代码/内部文档发给第三方)、延迟不可控、成本随用量线性增长,Fine-tuning 开源模型可以本地部署,解决这些问题。第二是技术学习层面:理解模型训练过程,对做 Agent、做 RAG、做 prompt 工程都有帮助,知道模型"为什么这样",而不只是知道"怎么用"。选 Llama3 是因为它在开源模型里综合能力最强,社区工具链(Unsloth、LLaMA-Factory)完善,上手成本低。

* * *

## 29. SFT、DPO、GRPO 分别是什么,区别在哪

**答:**

**SFT(Supervised Fine-Tuning,监督微调)**

用标注好的(问题, 回答)对直接训练模型,最大化正确答案的对数概率。

训练数据:(prompt, chosen_response) 对 目标:让模型学会在给定 prompt 时生成 chosen_response 这样的输出 优点:简单直接,数据易获取 缺点:只学"正确是什么",不学"为什么错的不好"

markdown 复制代码
**DPO(Direct Preference Optimization,直接偏好优化)**

用偏好对(同一 prompt 下,好回答 vs 差回答)训练,让模型增大好回答概率、降低差回答概率。

训练数据:(prompt, chosen_response, rejected_response) 三元组 目标:让 chosen 和 rejected 的概率差距最大化 优点:比 RLHF 更稳定(不需要单独训练 reward model) 缺点:需要人工标注偏好数据

markdown 复制代码
**GRPO(Group Relative Policy Optimization)**

对同一 prompt 采样多个响应,以组内相对质量作为奖励信号,不需要参考模型。DeepSeek-R1 训练使用了这个方法。

训练数据:(prompt, response_1, response_2, ..., response_n) 奖励:每个 response 相对于组内平均质量的得分 优点:不需要 reference model,内存效率更高;天然适合可验证奖励(数学/代码) 缺点:奖励函数设计复杂,不可验证任务难以应用

markdown 复制代码
| 维度              | SFT                | DPO                        | GRPO                    |
| --------------- | ------------------ | -------------------------- | ----------------------- |
| 数据形式            | (prompt, response) | (prompt, chosen, rejected) | (prompt, [多个responses]) |
| 需要 Reward Model | 否                  | 否                          | 否(内置奖励函数)               |
| 训练稳定性           | 最高                 | 高                          | 中                       |
| 适合场景            | 格式对齐、知识注入          | 风格偏好、安全对齐                  | 推理、数学、代码(可验证任务)         |

* * *

## 29. 为什么基于 Llama3 架构,数据集大小,评测指标

**答:** (参考框架,根据实际项目替换)

**为什么选 Llama3:**

-   开源,权重可商用(Llama3 Community License)
-   架构成熟,社区支持好(Unsloth、LLaMA-Factory 等训练框架完善)
-   在同等参数量下,Llama3 性能在开源模型中处于前列
-   中文支持:Llama3 词表中包含中文 token(比 Llama2 好很多)

**数据集大小参考:**

-   SFT:通常 1k--100k 条,高质量 > 低质量大量数据
-   DPO:几千到几万条偏好对
-   特定领域:500--5000 条高质量领域数据足以明显改变模型行为

**常用评测指标:**

| 任务类型 | 指标                    |
| ---- | --------------------- |
| 代码生成 | HumanEval Pass@1、MBPP |
| 数学推理 | GSM8K、MATH            |
| 中文理解 | C-Eval、CMMLU          |
| 对话质量 | MT-Bench(GPT-4 评判)    |
| 领域专项 | 自建测试集,人工评分            |

* * *

# 其他问题

* * *

## 30. 相比别人的优势是什么

**参考思路(根据实际情况组织):**

从三个层面回答:

**深度实践层面:** 不只是会用 AI 工具,而是深度参与了从工具使用到系统构建的全链路------自建 Coding Agent、GraphRAG、做过 fine-tuning,有端到端的工程经验,而不只停留在 prompt 工程层面。

**工程化思维层面:** 把 AI 能力工程化------skill 系统、上下文治理、多 Agent 编排、成本控制,这些都是把 AI 从"实验品"变成"生产可用系统"需要解决的工程问题,有实际落地经验。

**持续学习层面:** 跟踪最新进展(SDD、GRPO、GraphRAG),并能把新技术快速应用到实际项目,不只是了解概念。

* * *

## 31. 反问:AI 发展这么快,对业务最大的赋能点在哪

**参考答案:**

> 我认为最大的赋能点不在于模型本身有多强,而在于**AI 把知识工作的执行成本大幅压低**。一个高级工程师做架构决策需要 2 小时,AI 辅助后可能 30 分钟;一个产品经理写 PRD 需要一天,AI 辅助后可能半天。这种"执行成本下降"会让团队能够尝试更多方向,迭代更快。对业务来说,最大的赋能是**降低试错成本,提高迭代速度**,而不是让某一件事做得更好。

* * *

## 32. 反问:基模快速迭代,Agent 还有必要吗

**参考答案:**

> 我认为有,而且越来越必要。基模变强,解决的是"单次对话能做多复杂的事";Agent 解决的是"如何把多次交互、多个工具、多步流程组织成一个可靠的系统"。这是两个维度的问题。基模再强,也没法自动决定什么时候调用什么工具、怎么管理长期上下文、出错了怎么恢复------这些是 Agent 架构要解决的问题。实际上基模越强,Agent 能做的事就越复杂,两者是相互放大的关系,而不是替代关系。

* * *

*基于 2026 年 6 月技术现状整理,面试时用自身项目经验替换 Demo 里的示例效果更佳。*
相关推荐
梨子同志1 小时前
HTML
前端
ZhengEnCi1 小时前
Q06-导航按钮高级拟态玻璃效果构建完全指南
前端·css
weiwin1232 小时前
MAF 入门(6):人工审核(HITL)
agent
Apifox2 小时前
Apifox 6 月更新|Apifox CLI 全面升级、导入导出优化、OAuth 2.0 支持自动刷新令牌
前端·后端·测试
CodingSpace2 小时前
TypeScript 装饰器
前端
宸翰2 小时前
解决 uni-app App 端 vue-i18n 占位符丢失:封装跨端可用的 tf 格式化方法
前端·vue.js·uni-app
leeyi2 小时前
中间件系统:在 Agent 执行流中插入自定义逻辑
aigc·agent·ai编程
systemPro2 小时前
光储充系统数据流全解析:PV / ESS / GRID 数据怎么流转,线损怎么算
前端
古茗前端团队4 小时前
急招!前端|测试|后端|产品(名额多,速来)
前端·后端·架构