LLM 与 Agent 核心机制详解

2026-08-24
803327 分钟
...

LLM 与 Agent 核心机制详解

tags: llm, agent, ai

LLM 与 Agent 核心机制详解

面向前端 / 后端开发者的系统化梳理。每个概念都用 Go 或 JS 最小可运行示例 说明,配图(伪代码)讲清楚「它在解决什么问题」「实现原理的关键点」「生产环境要注意什么」。

目录

  1. LLM API
  2. KV Cache
  3. Agent Loop
  4. Tool Use
  5. Reasoning
  6. Planning
  7. Skills
  8. MCP
  9. Memory
  10. Subagent
  11. Multi-Agent
  12. Prompt Engineering
  13. Context Engineering
  14. 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_reasonstop(自然结束)/ tool_calls(要调工具)/ length(截断)。
  • 最大轮数:必须设上限,否则模型可能死循环把账户刷爆。
  • 状态:整个 messages 数组就是 agent 的"记忆",每轮追加 assistanttool 消息。

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+SSEstreamable 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               │  ← 能动手有循环
       └────────────────────┬────────────────────�

       ┌──────────┬───────────────────┬──────────┐
SkillsMemoryMCP  │   ←  agent 更专业有记忆能接外部
       └──────────┴────────┬──────────┴──────────┘

       ┌─────────────────────────────────────────┐
Subagent + Multi-Agent                 │  ←  agent 协作
       └────────────────────┬────────────────────┘

       ┌─────────────────────────────────────────┐
Harness Engineering                    │  ← 工程化可测可观测
       └─────────────────────────────────────────┘

读这张图从下往上:基础 → 单 agent 能力 → 扩展能力 → 协作 → 工程化。掌握这张图基本就覆盖了 LLM/Agent 开发的核心知识体系。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
AI2026-09-01
Deep Agents 01:何为 Agent Harness,以及如何开始
1、本篇任务:完成一份多步骤、带证据的技术调研 普通客服 Agent 的问题短、工具少、输出即时。技术调研或编码任务会持续很久,产生计划、搜索结果、文件和中间结论。Deep Agents 在 LangChain/LangGraph 之上预装规划、虚拟文件系统、上下文压缩和子 Agent,适合这类开放任务。 本课让 Agent 比较两种向量数据库,并交付一份可验证报告。 2、什么时候需要 Deep Agent 满足以下两项以上再考虑:任务...
学习
AI2026-09-01
Deep Agents 02:子 Agent、虚拟文件系统与长期记忆
1、本篇任务:让主管只看结论,让子 Agent 处理细节 技术调研会产生几十次搜索和大量文件。如果全部进入主管上下文,真正的目标会被噪音淹没。本课用两个子 Agent: 收集证据, 检查结论;主管负责计划与最终合成。 2、什么时候委派,什么时候直接调用工具 适合委派:子任务有多步;需要专门提示或工具;会产生大量中间结果;只需返回有限结论。不适合:一步查询;主管需要全部中间上下文;协调成本超过任务本身。 3、配置专门子 Agent Pyt...
学习
AI2026-09-01
Deep Agents 03:生产化、Sandbox、权限与上线验收
1、本篇任务:让 Deep Agent 在隔离环境中分析代码 只读研究 Agent 风险有限;编码 Agent 需要读写文件、安装依赖和执行测试。本课不讲如何让模型写更漂亮的代码,只讲执行环境、权限、恢复和上线验收。 2、先做威胁模型 资产包括源代码、用户文件、云凭证、生产网络和发布权限;攻击入口包括用户消息、仓库内容、网页、依赖包、MCP 返回和命令输出。 Prompt injection 不是靠一句 system prompt 解决...
学习
AI2026-09-01
LangChain 01:全景、原理与学习路线
1、本篇学完要得到什么 这一篇只解决三个问题:LangChain 到底负责什么;它与 LangGraph、Deep Agents、LangSmith 是什么关系;后面应按什么顺序学习。 贯穿整套课程的项目是“退款政策与订单助手”。它最终能够:回答知识库中的退款规则;查询当前用户的订单;生成结构化答复;对真正的退款操作进行人工审批;断线后恢复;通过评测后发布。 先记住一句话: 模型负责理解与生成,应用负责数据、权限、状态和副作用。 如果把...
学习
AI2026-09-01
LangChain 02:模型、消息与结构化输出
1、本篇任务:让模型输出成为程序可以依赖的合同 上一课只证明 Agent 能运行。本课暂时不接业务工具,只完成一个“客服分诊器”:输入用户问题,输出意图、紧急程度、是否需要人工和给用户的答复。 本课的核心不是学更多模型参数,而是理解三层合同:消息决定模型看到了什么;schema 决定程序期待什么;业务校验决定结果是否真的可用。 2、消息不是一段字符串,而是一条执行记录 一次工具型对话通常包含四种消息: | 类型 | 由谁产生 | 作用...
学习
AI2026-09-01
LangChain 03:工具与 Agent——从函数到可控行动
1、本篇任务:让 Agent 安全地读取订单 上一课得到结构化分诊结果,但模型不知道真实订单。本课增加一个只读工具 ,走通完整 Agent 循环,并把模型、工具包装和领域服务的责任分开。 完成后,用户问“我的 A100 发货了吗”,Agent 会选择工具;工具只按当前登录用户查询;模型基于工具结果回答。它仍然不能退款,因为我们没有提供写工具。 2、工具的本质是受 schema 约束的应用函数 一个好工具需要:稳定名称、清楚描述、窄输入...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录