LLM 与 Agent 核心机制详解
LLM 与 Agent 核心机制详解
tags: llm, agent, ai
LLM 与 Agent 核心机制详解
面向前端 / 后端开发者的系统化梳理。每个概念都用 Go 或 JS 最小可运行示例 说明,配图(伪代码)讲清楚「它在解决什么问题」「实现原理的关键点」「生产环境要注意什么」。
目录
- LLM API
- KV Cache
- Agent Loop
- Tool Use
- Reasoning
- Planning
- Skills
- MCP
- Memory
- Subagent
- Multi-Agent
- Prompt Engineering
- Context Engineering
- Harness Engineering
1. LLM API
是什么:LLM(大语言模型)通过 HTTP 接口对外提供服务。你发一段文本(prompt),它返回生成的文本(completion)。
前端类比:相当于 fetch(),但返回的不是 JSON 而是模型按概率生成的 token 序列。
关键点:
- chat 格式:messages 数组里有
system/user/assistant三种角色。system设定行为,user是用户输入,assistant是模型历史回复。 - 流式 vs 非流式:流式(SSE)边生成边推送,首 token 延迟低;非流式一次性返回完整结果,逻辑简单。
- token 计量:按输入 + 输出 token 数计费。中文 ≈ 1 字 ≈ 1.5~2 token。
Go 示例(调用 OpenAI 兼容接口):
package main
import (
"context"
"fmt"
openai "github.com/sashabaranov/go-openai"
)
func main() {
client := openai.NewClient("sk-xxx") // 也支持 DeepSeek / Azure / Ollama 等兼容服务
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: "gpt-4o-mini",
Messages: []openai.ChatCompletionMessage{
{Role: "system", Content: "你是一个简洁的助手,回答不超过 50 字。"},
{Role: "user", Content: "用一句话解释 goroutine"},
},
Temperature: 0.7,
},
)
if err != nil {
panic(err)
}
fmt.Println(resp.Choices[0].Message.Content)
fmt.Printf("用了 %d tokens\n", resp.Usage.TotalTokens)
}
JS 示例(流式 + Fetch SSE):
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4o-mini",
stream: true, // ← 开启流式
messages: [
{ role: "system", content: "你是一个简洁的助手" },
{ role: "user", content: "用一句话解释 Promise" },
],
}),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
// 逐行解析 SSE: "data: {...}\n\n"
for (let line of buf.split("\n")) {
if (line.startsWith("data: ") && line !== "data: [DONE]") {
const json = JSON.parse(line.slice(6));
process.stdout.write(json.choices[0].delta.content ?? "");
}
}
buf = buf.slice(buf.lastIndexOf("\n") + 1);
}
生产注意:流式必须处理 data: [DONE] 哨兵;非流式要设超时(模型可能 hang);token 计费要看 usage 字段,prompt 缓存命中后输入 token 会大幅降价。
2. KV Cache
是什么:Transformer 推理时,每生成一个新 token,都要为之前所有 token 重新算一遍注意力(Q/K/V 矩阵)。KV Cache 把之前算好的 K/V 缓存下来,下个 token 只算自己这一个的 Q,再去查缓存的 K/V,省掉 80%+ 的计算量。
前端类比:相当于 React 里 useMemo —— 依赖没变就不重算,只算新输入。
关键点:
- 位置敏感:prompt 哪怕只动一个字,前面所有 token 的 K/V 全部失效(因为 attention 是 token 间的相对位置)。所以"在 system prompt 中间改一句话"会让缓存命中率归零。
- 前缀缓存:很多服务(OpenAI、Claude、DeepSeek)会自动缓存相同前缀的 prompt,命中时输入 token 价格大幅下降(OpenAI 缓存命中是 1/10 价格)。
- 实现细节:通常每 N 个 token 做一次 checkpoint,缓存到显存里;超长上下文(>32K)时缓存本身就能吃掉几 GB。
Go 示例(演示"前缀稳定"对缓存命中的影响):
package main
import (
"fmt"
"strings"
)
// MockKVCache 模拟 KV Cache 命中逻辑
type MockKVCache struct {
prefix string
hits int
}
func (c *MockKVCache) BuildKV(prompt string) {
// 实际中:按 token 序列前缀匹配,命中则复用前面所有 K/V
if strings.HasPrefix(prompt, c.prefix) {
c.hits++
fmt.Printf("✅ 命中缓存 (累计 %d 次),只算新增 token\n", c.hits)
} else {
c.prefix = prompt[:len(c.prefix)] // 简化:取前 N 字符
fmt.Println("❌ 缓存失效,重新计算全部 K/V")
}
}
func main() {
cache := &MockKVCache{}
cache.BuildKV("你是助手。规则 1:... 规则 2:... 用户问题:北京天气?")
cache.BuildKV("你是助手。规则 1:... 规则 2:... 用户问题:上海天气?")
// ↑ 完全相同的前缀 → 命中
cache.BuildKV("你是助手。规则 1:...(改了一个字)... 用户问题:上海天气?")
// ↑ 改了 system prompt 中间 → 全部失效
}
生产建议:
- 把稳定的内容放最前面(system prompt、工具定义、长文档),把会变的内容放最后(用户当前问题)。
- OpenAI 的
prompt_cache_key可以显式告诉服务方哪些请求应该共享缓存。
3. Agent Loop
是什么:Agent = LLM + 工具 + 一个 while 循环。每轮把"对话历史 + 工具结果"喂给 LLM,看它要不要再调工具,直到它说"我答完了"。
前端类比:相当于 React 里 useEffect 的依赖循环 —— 状态变了再跑一轮,跑完看是否收敛。
关键点:
- 终止条件:模型返回的
finish_reason是stop(自然结束)/tool_calls(要调工具)/length(截断)。 - 最大轮数:必须设上限,否则模型可能死循环把账户刷爆。
- 状态:整个 messages 数组就是 agent 的"记忆",每轮追加
assistant和tool消息。
JS 示例(最简 agent loop):
async function agentLoop(userMessage, tools = {}) {
const messages = [
{ role: "system", content: "你可以调用工具。完成任务后直接给最终答案。" },
{ role: "user", content: userMessage },
];
for (let turn = 0; turn < 10; turn++) { // ← 必设上限
const resp = await callLLM({ messages, tools: Object.values(tools) });
const msg = resp.choices[0].message;
messages.push(msg);
if (msg.tool_calls) {
// 并行执行所有 tool calls
for (const call of msg.tool_calls) {
const fn = tools[call.function.name];
const result = await fn(JSON.parse(call.function.arguments));
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify(result),
});
}
continue; // ← 进入下一轮,让 LLM 看工具结果
}
// 没有 tool_calls → 自然结束
return msg.content;
}
throw new Error("超过最大轮数,可能死循环");
}
生产注意:
- 每轮累计 token 数会膨胀(KV Cache 帮不了跨轮),第 5 轮之后可能就上万 token 了。
- LLM 可能会"幻觉工具"或"重复调同一个工具",需要检测并打断。
- 工具调用本身可能失败(超时、抛错),要做好重试和降级。
4. Tool Use
是什么:让 LLM 不只是返回文本,还能"调用函数"。原理是:你在请求里声明一组工具 schema(含名字、描述、参数 JSON Schema),LLM 不真执行函数,只返回一个结构化 tool_calls 数组,由你的代码在沙箱里执行。
前端类比:相当于 React 里 <button onClick={handler}> —— 组件本身不跑逻辑,只是声明意图,真正的处理函数在外部。
关键点:
- 描述就是 prompt:工具的
description字段会被拼进 LLM 的上下文,它决定 LLM 会不会、何时调这个工具。描述写得差 = 工具根本不被调用。 - JSON Schema 约束:参数必须严格遵循你给的 schema,否则 LLM 会编出非法 JSON。
- 副作用隔离:LLM 不应该能直接执行任意代码,所有危险操作(删文件、付款)必须经你的代码做权限检查。
JS 示例:
const tools = [
{
type: "function",
function: {
name: "get_weather",
description: "查询指定城市的当前天气。当用户问天气、温度、是否下雨时调用。",
parameters: {
type: "object",
properties: {
city: { type: "string", description: "城市名,如 '北京'" },
unit: { type: "string", enum: ["celsius", "fahrenheit"], default: "celsius" },
},
required: ["city"],
},
},
},
];
const resp = await callLLM({
messages: [{ role: "user", content: "北京现在多少度?" }],
tools,
});
// resp.choices[0].message.tool_calls[0].function.arguments
// → '{"city":"北京","unit":"celsius"}'
生产建议:
- 工具描述写"何时调用 / 何时不调用 / 参数单位 / 示例"。
- 参数尽量少、扁平;嵌套超过 2 层 LLM 经常写错。
- 工具调用结果太长时先 summarize 再塞回 messages,否则几轮就撑爆上下文。
5. Reasoning
是什么:让 LLM 在最终回答前先"自言自语"一串思考过程,提高复杂任务的准确率。代表技术:Chain-of-Thought(CoT)、Tree-of-Thoughts、Self-Consistency、o1/o3 类的隐式推理。
前端类比:debug 时先 console.log 中间值再下结论,而不是直接猜结果。
关键点:
- 显式 vs 隐式:显式 CoT 你能看到
Reasoning: ...;隐式(o1/o3)模型内部跑了推理但只返回结论。 - 代价:推理消耗更多输出 token(更贵、更慢),但准确率显著上升。
- 何时用:数学、多步规划、代码 debug、复杂决策。简单问答不需要。
Go 示例(对比 Zero-shot vs CoT):
package main
import (
"context"
"fmt"
openai "github.com/sashabaranov/go-openai"
)
func ask(client *openai.Client, prompt string, useCoT bool) string {
sys := "直接给出最终答案,不要解释。"
if useCoT {
sys = "先一步步思考,列出推理过程,最后单独一行写 '答案:XXX'。"
}
resp, _ := client.CreateChatCompletion(context.Background(),
openai.ChatCompletionRequest{
Model: "gpt-4o-mini",
Messages: []openai.ChatCompletionMessage{
{Role: "system", Content: sys},
{Role: "user", Content: prompt},
},
})
return resp.Choices[0].Message.Content
}
func main() {
q := "小明有 5 个苹果,吃了 2 个,又买了 3 倍数量的苹果,现在有几个?"
fmt.Println("--- Zero-shot ---")
fmt.Println(ask(nil, q, false))
fmt.Println("--- CoT ---")
fmt.Println(ask(nil, q, true))
}
6. Planning
是什么:让 LLM 先制定计划("我要做这几步"),再按计划执行。代表范式:ReAct、Plan-and-Execute、Reflexion。
前端类比:写代码前先列 TODO list,每完成一项打个勾。
关键点:
- ReAct(Reason + Act):边想边做,每步
Thought: ... → Action: ... → Observation: ...,适合探索性任务。 - Plan-and-Execute:先一次性生成完整计划,再顺序执行。计划可缓存复用,适合确定性任务。
- 失败重规划:执行失败时让 LLM 重新生成计划(Reflexion),而不是直接放弃。
JS 示例(Plan-and-Execute):
async function planAndExecute(task) {
// 1) 计划阶段
const planResp = await callLLM({
messages: [{
role: "user",
content: `任务:${task}\n请输出 JSON 数组,每项是一个步骤,例如:\n[{"step":1, "action":"search", "input":"..."}]\n不要执行,只要计划。`,
}],
});
const plan = JSON.parse(planResp.choices[0].message.content);
// 2) 执行阶段
const results = [];
for (const step of plan) {
const result = await executeStep(step);
results.push(result);
// 3) 检查是否需要重新规划
if (result.failed) {
const newPlan = await rePlan(task, results);
// ... 用新计划继续
}
}
return synthesize(results);
}
7. Skills
是什么:把"做某件事的专业知识 + 工具集"打包成可复用的模块。LLM 可以根据当前任务动态加载对应的 skill(按需注入 prompt 和工具)。
前端类比:相当于 VS Code 的扩展(extension),按需启用,每个扩展带自己的命令、配置、文档。
关键点:
- Skill = 文档 + 工具 + 配置:文档是给 LLM 看的"使用说明",工具是它能调的动作。
- 注册表模式:所有 skill 注册到中心 registry,主 agent 根据任务描述选 skill 加载。
- 节省 token:不用的 skill 不进 prompt,避免上下文膨胀。
Go 示例:
package main
import "fmt"
type Skill struct {
Name string
Description string // ← LLM 用这个判断何时加载
Tools []Tool
Prompt string // 加载后注入 system prompt
}
type Registry struct {
skills map[string]Skill
}
func (r *Registry) Register(s Skill) { r.skills[s.Name] = s }
func (r *Registry) Load(names []string) []Skill {
out := []Skill{}
for _, n := range names {
out = append(out, r.skills[n])
}
return out
}
func main() {
reg := &Registry{skills: map[string]Skill{}}
reg.Register(Skill{
Name: "code-review",
Description: "审查代码:找 bug、安全问题、性能问题。代码相关任务时启用。",
Prompt: "你是一个严格的代码审查员...",
Tools: []Tool{{Name: "read_file"}, {Name: "run_linter"}},
})
reg.Register(Skill{
Name: "data-analysis",
Description: "分析 CSV / 数据集,生成统计和图表。",
Prompt: "你是一个数据分析师...",
Tools: []Tool{{Name: "run_sql"}, {Name: "plot_chart"}},
})
// 主 agent 根据用户意图选 skill
loaded := reg.Load([]string{"code-review"})
fmt.Printf("已加载 %d 个 skill\n", len(loaded))
}
8. MCP(Model Context Protocol)
是什么:Anthropic 2024 年提出的开放协议,标准化"LLM 应用 ↔ 工具/数据源"的通信方式。可以理解为 Agent 时代的 USB-C —— 一套协议,所有工具都能插。
前端类比:相当于 Web 的 REST API —— 大家都按 HTTP + JSON 来,客户端不关心服务端用什么语言实现。
关键点:
- 三大原语:
Resources(只读数据,如文件)、Tools(可执行函数)、Prompts(预制 prompt 模板)。 - Client / Server 架构:MCP server 暴露能力,MCP client(Claude Desktop、Cursor、Cline)连接使用。
- 传输:本地用
stdio(子进程 stdin/stdout),远程用HTTP+SSE或streamable HTTP。
JS 示例(最简 MCP client):
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
// 启动一个 MCP server 子进程并连接
const transport = new StdioClientTransport({
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
});
const client = new Client({ name: "my-agent", version: "1.0" }, { capabilities: {} });
await client.connect(transport);
// 列出 server 提供的所有工具
const { tools } = await client.listTools();
console.log("可用工具:", tools.map(t => t.name));
// 调用工具
const result = await client.callTool({
name: "read_file",
arguments: { path: "/tmp/hello.txt" },
});
console.log(result.content[0].text);
生产价值:
- 解耦:工具作者写一次 MCP server,所有兼容 client 都能用。
- 安全边界:MCP server 跑在独立进程,权限容易隔离(沙箱)。
- 生态复用:社区已有大量 MCP server(GitHub、Postgres、Playwright、Puppeteer...)。
9. Memory
是什么:让 agent 跨对话/跨任务记住信息。分三层:
- 短期(in-context):当前对话的 messages。
- 长期(持久化):用户偏好、历史任务,存数据库/向量库,下次对话开头检索注入。
- 情景(episodic):过去的"我做过什么、结果怎样",用于反思。
前端类比:短期 = React state,长期 = localStorage / DB,情景 = Git log。
关键点:
- 写入时机:对话结束时总结、用户明确说"记住 XXX"、任务完成后反思。
- 检索:用 embedding 相似度搜索(semantic),而不是关键词匹配。
- 注入方式:拼到 system prompt 开头,限制总量(否则挤占主任务上下文)。
Go 示例:
package main
import "fmt"
type Memory struct {
ShortTerm []Message // 当前对话
LongTerm []Fact // 持久事实
}
type Fact struct {
Content string
Score float64 // 相关性分数
}
func (m *Memory) BuildSystemPrompt() string {
// 1) 取出最相关的 N 条长期记忆
facts := m.retrieveTopK(m.LongTerm, 5)
// 2) 拼到 system prompt 开头
prompt := "以下是关于用户的相关记忆:\n"
for _, f := range facts {
prompt += "- " + f.Content + "\n"
}
prompt += "\n你是一个有帮助的助手。"
return prompt
}
func (m *Memory) retrieveTopK(facts []Fact, k int) []Fact {
// 实际:用 embedding 算 cosine 相似度,返回 top-k
return facts[:k]
}
func (m *Memory) Remember(fact Fact) {
m.LongTerm = append(m.LongTerm, fact)
fmt.Printf("已记住:%s(现在共 %d 条长期记忆)\n", fact.Content, len(m.LongTerm))
}
func main() {
mem := &Memory{}
mem.Remember(Fact{Content: "用户偏好简短回答"})
mem.Remember(Fact{Content: "用户用 Go 做后端"})
fmt.Println(mem.BuildSystemPrompt())
}
10. Subagent
是什么:主 agent 把子任务委派给一个全新的、独立上下文的子 agent,自己不参与执行,只接收最终结果。
前端类比:相当于主函数调用一个 worker 线程 —— 主线程不阻塞,子线程跑完后回调。
关键点:
- 上下文隔离:子 agent 有自己干净的 messages 数组,主 agent 的历史不会泄露进去(节省 token + 避免干扰)。
- 任务粒度:适合"明确、独立、可验证"的子任务("调研 XX"、"写 XX 单元测试")。
- 工具权限:子 agent 通常只被授权它需要的工具(最小权限原则)。
JS 示例:
async function delegateTask(parentAgent, task, tools = []) {
// 全新上下文
const subMessages = [
{ role: "system", content: `你是子 agent,专心完成这个任务:${task}` },
];
for (let i = 0; i < 5; i++) {
const resp = await callLLM({ messages: subMessages, tools });
subMessages.push(resp.choices[0].message);
if (resp.choices[0].message.tool_calls) {
for (const call of resp.choices[0].message.tool_calls) {
const result = await executeTool(call);
subMessages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) });
}
} else {
return resp.choices[0].message.content; // ← 返回给父 agent
}
}
}
// 父 agent 的工具表里有这个"委派"工具
const parentTools = [
{ type: "function", function: {
name: "delegate",
description: "把子任务交给一个全新的 agent 执行,适合独立、明确的任务。",
parameters: { type: "object", properties: { task: { type: "string" } }, required: ["task"] },
}},
];
11. Multi-Agent
是什么:多个 agent 协同解决复杂问题,每个 agent 有不同角色,互相通信。有几种范式:
- 层级式:orchestrator 派活给 worker agents。
- 对等式:agents 互相讨论(debate),多数决。
- 流水线:agent A 的输出是 agent B 的输入(assembly line)。
前端类比:相当于微服务架构 —— 每个服务独立,但通过 API 协作。
关键点:
- 通信协议:常用 JSON 消息总线,或共享黑板(blackboard)模式。
- 冲突解决:多 agent 可能给出矛盾答案,需要一个 judge / aggregator。
- 成本:N 个 agent = N 倍 token + N 倍延迟,不是越多越好。
Go 示例(orchestrator + 2 个 worker):
package main
import "fmt"
type Agent interface {
Role() string
Run(input string) string
}
type Researcher struct{}
func (Researcher) Role() string { return "researcher" }
func (Researcher) Run(input string) string { return "调研结果:" + input }
type Writer struct{}
func (Writer) Role() string { return "writer" }
func (Writer) Run(input string) string { return "基于调研写文章:" + input }
func Orchestrator(task string, agents []Agent) string {
fmt.Println("Orchestrator: 拆解任务")
// 1) 派给 researcher
research := agents[0].Run(task)
fmt.Printf(" [%s] %s\n", agents[0].Role(), research)
// 2) 派给 writer
article := agents[1].Run(research)
fmt.Printf(" [%s] %s\n", agents[1].Role(), article)
return article
}
func main() {
agents := []Agent{Researcher{}, Writer{}}
result := Orchestrator("AI Agent 的未来", agents)
fmt.Println("最终结果:", result)
}
12. Prompt Engineering
是什么:通过精心设计 prompt 来引导 LLM 输出你想要的结果。不是"调参",是"写作"。
关键技巧:
- 角色设定:
你是一个有 10 年经验的后端架构师比你是助手效果好得多。 - Few-shot:在 prompt 里给 2~3 个"输入→输出"示例,比纯指令靠谱。
- 结构化输出:明确说"用 JSON 输出,字段是...",甚至用 JSON Schema 约束。
- 负面约束:
不要解释、不要道歉、不要超过 50 字比"请简洁"有效。 - 分隔符:用 ``` 或 ### 区分 prompt 的不同部分,防止 LLM 把指令和数据搞混。
JS 示例(对比差 prompt vs 好 prompt):
// ❌ 差 prompt
const bad = "帮我看看这段代码";
// ✅ 好 prompt
const good = `
你是一个严格的 TypeScript 代码审查员,专注找出 bug 和性能问题。
## 规则
1. 只输出问题列表,不要寒暄
2. 每条问题格式:行号 + 严重程度(critical/major/minor)+ 描述 + 修复建议
3. 不要给"总体还不错"这类废话
## 代码
\`\`\`typescript
${code}
\`\`\`
## 输出格式(JSON)
[{"line": 12, "severity": "major", "issue": "...", "fix": "..."}]
`;
13. Context Engineering
是什么:比 Prompt Engineering 更进一步 —— 关注整个上下文窗口的工程化:什么该进、什么该出、怎么排版、怎么压缩。Anthropic 2025 年提出的概念。
核心原则:
- 少即是多:上下文越短,模型越专注、越快、越便宜。
- Just-in-time:不要把所有信息一次性塞进去,按需检索、动态注入。
- 结构化 > 自然语言:表格、JSON、Markdown 比一段话更易被 LLM 解析。
常用技巧:
- 压缩:用 LLM 把长对话/长文档总结成要点再注入。
- 分块:大文档按章节切,每次只塞相关章节。
- 截断:超长时丢掉最旧的消息或中间的"已完成"任务。
- 缓存:稳定前缀放最前,触发 KV Cache 命中。
Go 示例(上下文管理工具):
package main
import (
"fmt"
"strings"
)
type Context struct {
System string
Messages []map[string]string
MaxTokens int
}
func (c *Context) Append(role, content string) {
c.Messages = append(c.Messages, map[string]string{
"role": role, "content": content,
})
}
// SlidingWindow: 超出 token 上限时,丢最旧的非 system 消息
func (c *Context) Trim() {
for c.estimateTokens() > c.MaxTokens && len(c.Messages) > 2 {
c.Messages = append(c.Messages[:1], c.Messages[2:]...)
// 保留第一条 + 从第三条开始(假设第一条是 user 任务,丢第一条 user/assistant 对)
}
}
func (c *Context) estimateTokens() int {
total := len(c.System) / 2 // 中文粗估 1 字 = 2 token
for _, m := range c.Messages {
total += len(m["content"]) / 2
}
return total
}
// SummarizeOld: 用 LLM 总结前 N 条消息
func (c *Context) SummarizeOld(n int) {
old := c.Messages[:n]
joined := []string{}
for _, m := range old {
joined = append(joined, fmt.Sprintf("%s: %s", m["role"], m["content"]))
}
summary := summarize(strings.Join(joined, "\n")) // 调用 LLM 总结
c.Messages = append([]map[string]string{
{"role": "system", "content": "以下是早期对话的摘要:\n" + summary},
}, c.Messages[n:]...)
}
func summarize(_ string) string { return "(这里调用 LLM 做总结)" }
func main() {
ctx := &Context{MaxTokens: 8000}
ctx.System = "你是助手"
ctx.Append("user", "你好")
fmt.Printf("当前 tokens: %d\n", ctx.estimateTokens())
}
14. Harness Engineering
是什么:Harness = "挽具、框架"。Harness Engineering 是把整个 LLM 应用工程化的实践:评测(eval)、可观测性(observability)、回归测试(regression)、版本管理。
前端类比:相当于前端的 CI/CD + 监控 —— 不能光"能跑",还要"能测、能看、能回滚"。
关键组件:
- Eval suite:一组标注好的输入-期望输出,自动跑测试集衡量模型/提示词改动后的效果。
- Trace:记录每次 agent loop 里每轮的 prompt、工具调用、token 数、耗时、错误。Langfuse / Helicone 是常用平台。
- 回归测试:模型升级或 prompt 改了一句话,要重跑 eval 确保没退化。
- A/B:同一任务让两个模型/两个 prompt 各跑一遍,对比质量、成本、延迟。
JS 示例(最小 eval harness):
// 测试集:每条 = { input, expected_keywords, must_not_contain }
const evalSet = [
{
input: "北京天气怎么样?",
expectedKeywords: ["北京", "温度", "天气"],
mustNotContain: ["我不知道", "无法访问"],
},
{
input: "写一个 Go 的 hello world",
expectedKeywords: ["package main", "func main", "Println"],
mustNotContain: ["Python"],
},
];
async function runEval(agentFn) {
const results = [];
for (const tc of evalSet) {
const output = await agentFn(tc.input);
const hitKeywords = tc.expectedKeywords.every(k => output.includes(k));
const noForbidden = !tc.mustNotContain.some(k => output.includes(k));
results.push({
input: tc.input,
pass: hitKeywords && noForbidden,
output: output.slice(0, 100),
});
}
const passRate = results.filter(r => r.pass).length / results.length;
console.log(`通过率:${(passRate * 100).toFixed(1)}%`);
results.filter(r => !r.pass).forEach(r => {
console.log("❌", r.input, "→", r.output);
});
}
// 用法:跑测试
await runEval(myAgent);
生产建议:
- 每次发版前必跑 eval,否则线上翻车了都不知道。
- 评测集要包含典型 + 边界 + 反例三类样本。
- 模型升级(新版本 API)后第一件事就是跑 eval 看退化没退化。
总结:14 个概念的关系
┌─────────────────────────┐
│ Prompt Engineering │ ← 怎么写 prompt
└────────────┬────────────┘
↓
┌─────────────────────────┐
│ Context Engineering │ ← 怎么管理整个上下文
└────────────┬────────────┘
↓
┌─────────────────────────────────────────┐
│ LLM API + KV Cache │ ← 基础设施
└────────────────────┬────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Reasoning + Planning │ ← 让模型会想、会规划
└────────────────────┬────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Tool Use + Agent Loop │ ← 能动手、有循环
└────────────────────┬────────────────────�
↓
┌──────────┬───────────────────┬──────────┐
│ Skills │ Memory │ MCP │ ← 让 agent 更专业、有记忆、能接外部
└──────────┴────────┬──────────┴──────────┘
↓
┌─────────────────────────────────────────┐
│ Subagent + Multi-Agent │ ← 多 agent 协作
└────────────────────┬────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Harness Engineering │ ← 工程化、可测可观测
└─────────────────────────────────────────┘
读这张图从下往上:基础 → 单 agent 能力 → 扩展能力 → 协作 → 工程化。掌握这张图基本就覆盖了 LLM/Agent 开发的核心知识体系。
如果您觉得这篇文章有帮助,请点个赞吧~
评论
请登录后发表评论
去登录