Skip to content
编程开物
编程开物

一段代码,一段Prompt,一物成型。 探索编程、AI应用、架构设计与产品设计,分享工程实践,记录技术学习、工作总结与产品创造。

  • 首页
  • 编程基础
    • 计算机网络
  • 人工智能
    • AI概念和理论
    • 人工智能应用
    • 数据工程
  • 企业架构
    • 传统应用架构设计
    • AI应用架构设计
  • 项目管理
  • 产品设计
  • 技术教程
  • 行业动态
  • 关于我
编程开物

一段代码,一段Prompt,一物成型。 探索编程、AI应用、架构设计与产品设计,分享工程实践,记录技术学习、工作总结与产品创造。

第 2 章 LLM Application 的基本架构

编码者, 2026年9月16日2026年9月16日

本章目标:掌握一个 LLM 应用(不是 Agent,只是应用)的基本组成与核心机制——模型、提示词、上下文、工具、内存、状态、运行时;并理解 Chat Completion、Streaming、Structured Output、Tool Calling、错误处理、重试、超时、模型路由和成本管理这些基础工程问题。这些是所有 LLM 应用——包括 Agent——的共同底座。


2.1 LLM Application 的基本组成

在讨论 Agent 之前,我们必须先把”一个普通的 LLM 应用”搞明白。因为 Agent 不是从天上掉下来的新物种,它是 LLM 应用的一种更复杂的形态。理解了 LLM 应用的地基,Agent 的架构才能建立在坚实的基础上。

一个标准的 LLM Application 由七个基本部分组成:

2.1.1 Model(模型)

模型是核心计算单元,负责把输入的 token 序列转换成输出的 token 序列。在工程上,模型通过 API 访问,常见的有 Anthropic 的 Claude、OpenAI 的 GPT、Google 的 Gemini,以及本地部署的开源模型(Llama、Qwen、DeepSeek 等)。

对应用开发者而言,”模型”不是一个抽象的神经网络,而是一个可调用的 API 端点,它的接口契约是:

输入:消息列表(messages)+ 参数(temperature, max_tokens, tools 等)
输出:生成结果(文本 / 工具调用请求)

这里有一个容易被忽视但极其重要的认知:模型是一个”无状态”的函数。它不记得上一次调用、不保存任何状态、不产生任何副作用。每一次调用,它都只根据”这次给它的输入”来生成输出——给定相同输入,其输出是服从同一分布的一次采样,因此结果并不完全确定(这与温度和随机采样有关)。这个”无状态性”是 LLM 应用(尤其是 Agent)所有架构决策的起点——正因为模型无状态,所以”记忆””上下文””会话”这些概念,全都需要应用层来自己实现。

2.1.2 Prompt(提示词)

Prompt 是给模型的”指令”。它告诉模型:你是谁、要做什么、有什么约束、用什么格式输出。

Prompt 分为几个层次:

  • System Prompt(系统提示词):定义模型的角色和全局规则,通常固定不变或变化很少。
  • User Message(用户消息):用户的具体请求。
  • Assistant Message(助手消息):模型的历史回复,用于维持对话上下文。

Prompt 工程(Prompt Engineering)曾经被过度神化,但它的本质其实很朴素:用清晰的、结构化的语言,把任务和约束表达清楚。随着模型能力的提升,Prompt 的重要性在”写出一个好 Prompt”这个层面下降了,但在”组织好上下文”这个层面上升了——后者正是 Context Engineering 的范畴。

一个值得记住的判断:Prompt 决定模型”想做什么”,而上下文决定模型”能知道什么”。前者是意图,后者是信息。很多 LLM 应用效果不佳,问题往往不在 Prompt 写得不够”花哨”,而在上下文给的信息不够准确、不够完整。

2.1.3 Context(上下文)

Context 是模型在生成时”看到”的全部内容,包括 System Prompt、对话历史、工具结果、文件内容等。可以说,Context 就是模型的工作内存(working memory)——它直接决定了模型的输出质量。

Context 的关键约束是大小有限(受 Context Window 限制)和成本递增(token 越多越贵)。因此,管理 Context 是 LLM 应用中最核心的工程问题之一。

2.1.4 Tool(工具)

Tool 是模型可以调用的外部函数。通过 Tool Calling,模型能够突破”只能生成文本”的限制,去执行真实世界的动作。

工具在技术上是一个带 JSON Schema 的函数定义,模型看到这些定义后,可以在合适的时候请求调用某个工具。工具是”模型”与”真实世界”之间的桥梁——它让模型从”只会说”进化到”能做事”。

2.1.5 Memory(记忆)

Memory 是应用跨会话持久化保存的信息。它与上一节的 Context 要严格区分:会话内的对话历史、工具结果属于 Context(工作内存)范畴;Memory 专指跨会话保存、下次还能取回来的信息——比如用户偏好、项目知识,通常通过数据库 + 检索(如向量检索)实现。

注意:LLM 本身是无状态的。它不会记住上一次对话说了什么。所谓的”记忆”,完全是应用层通过”把历史存起来、下次再塞回上下文”来实现的。这个”记忆是外挂的”认知很重要——它意味着记忆的可靠性、检索的准确性,都是应用层要负责的工程问题,而非模型的”天赋”。

2.1.6 State(状态)

State 是应用运行时的状态,比如:当前进行到哪一步、任务队列、会话 ID、检查点等。State 和 Memory 的区别在于:State 更偏”运行时进度”,Memory 更偏”持久化知识”。但在实践中两者经常交织——比如一个”任务进行到第 3 步”的信息,既是运行时状态,也可能需要持久化以便恢复。

2.1.7 Runtime(运行时)

Runtime 是连接以上所有组件的”胶水层”:它负责组织请求、调用模型 API、执行工具、管理状态、处理错误。可以这么说:前六个组件是”零件”,Runtime 是”把它们装配成能跑的机器的流水线”。在 Agent 语境下,这个 Runtime 就是第 1 章所说的 Harness——Harness 可以理解为 Agent 的 Runtime,只是它额外承担了权限、会话、可观测性等更多职责。


2.2 Chat Completion 架构

Chat Completion 是最基本的 LLM 调用模式:给一个消息列表,得到一个回复。它的 API 请求结构(以 Anthropic Messages API 为例,模型 ID 以最新可用版本为准):

const response = await anthropic.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  system: "你是一个乐于助人的助手。",
  messages: [
    { role: "user", content: "你好,介绍一下你自己。" },
    { role: "assistant", content: "你好,我是一个 AI 助手。" },
    { role: "user", content: "你能帮我做什么?" }
  ]
});

这个简单的结构背后,有几个关键点:

  1. messages 是核心输入:模型看到的是完整的消息列表,它根据这个列表生成下一个 assistant 消息。
  2. system 是全局指令:独立于对话历史,通常更”稳定”。
  3. max_tokens 是输出上限:控制回复的最大长度,也间接控制成本。

这里要重点讲一下消息角色(Message Role)的设计。Messages API 之所以用”角色”来组织消息,是因为它精确地模拟了”对话”这个结构:

  • system:系统指令,是”上帝视角”的全局规则。注意它不在 messages 数组里,而是一个独立的顶层参数,不属于对话双方。
  • user:用户说的话,是任务和需求的来源。
  • assistant:模型说的话,包括文本回复和工具调用请求。

上面的示例没有用到工具,所以只出现了 user 和 assistant 两种角色。当引入工具调用后(见 2.5 节),消息里还会出现两类特殊的内容块——tool_use 和 tool_result——但它们不是独立的”角色”:tool_use 承载在 assistant 消息里,tool_result 承载在 user 消息里。

因此准确地说,Messages API 的 messages 数组里只有 user 和 assistant 两种角色。而工具调用的成对关系——assistant 消息里带 id 的 tool_use,之后必须有一条带相同 tool_use_id 的 tool_result 来”回应”它——正是 Agent 架构的基石。这个配对关系一旦断裂(比如工具结果丢失),模型就会困惑,这正是第 19 章源码分析中 yieldMissingToolResultBlocks 要处理的问题。

Chat Completion 的局限也很明显:它是一次性的。模型回复完就结束了,不会自己去”做更多的事”。这正是 Agent 要突破的——通过 Tool Calling 和循环,让”一次回复”变成”持续工作”。


2.3 Streaming

Streaming(流式输出)是 LLM 应用用户体验的关键。默认情况下,API 会等模型生成完全部内容后一次性返回,这会导致用户面对一个”转圈圈”的界面几十秒。

Streaming 改变了这一点:模型每生成一个 token,就立即把这个 token 推送出来。用户看到的是”逐字逐句”地涌现,体验上像是真人在打字。

Streaming 的技术实现是 Server-Sent Events(SSE) 或分块传输:

const stream = await anthropic.messages.stream({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "写一首诗" }]
});

stream.on("text", (text) => {
  process.stdout.write(text); <em>// 逐个 token 输出</em>
});

Streaming 带来的架构挑战,远比”把输出打到屏幕上”复杂:

2.3.1 状态管理

流式过程中,应用需要维护”已生成的完整文本”,以便流结束后做后续处理(比如把完整回复存入数据库、生成摘要)。你不能只把 token 打出去就不管了——完整内容必须被累积。

2.3.2 工具调用与流式的冲突

这是最棘手的部分。当模型要调用工具时,它输出的是结构化的 JSON(工具调用),而不是文本。而这个 JSON 是逐个 token 流式生成的。这意味着应用必须在流式过程中”攒” JSON——接收一个个 partial_json 片段,直到攒齐了完整的 JSON,才能解析出工具名和参数。

<em>// 简化写法:仅处理单个工具调用;并行工具调用需按 content block index 分别累积</em>
let toolCallJson = "";
for await (const event of stream) {
  if (event.type === "input_json_delta") {
    toolCallJson += event.partial_json;  <em>// 攒 JSON</em>
  }
  if (event.type === "content_block_stop") {
    const toolCall = JSON.parse(toolCallJson);  <em>// 攒齐了,解析</em>
  }
}

所以,一个成熟的 LLM 应用,其流式处理层实际上是一个复杂的状态机,需要区分”文本流”和”工具调用流”两种状态。

2.3.3 错误处理

流式传输中如果网络中断,应用需要处理”半截回复”的情况——已经收到了一半内容,剩下的没了。这时要么重试(从头再来),要么给用户展示”已生成的部分 + 中断提示”。

一个成熟的做法是:文本走流式,工具调用走”准流式”——先流式收集完整 JSON,解析出工具调用后执行,执行结果再作为新消息发起下一轮。


2.4 Structured Output

Structured Output(结构化输出)让模型输出符合 JSON Schema 的内容,便于程序解析。前面提到它有三种实现方式:Prompt 约束、JSON Mode、Constrained Decoding。

2.4.1 Prompt 约束(最弱)

在 Prompt 里写”请只输出 JSON”。简单,但不可靠——模型偶尔会”不听话”,在 JSON 前后加说明文字、漏掉引号、输出非法 JSON。

2.4.2 JSON Mode(中等)

API 层面提供约束,保证输出是合法 JSON。比 Prompt 约束可靠,但不保证 JSON 的 schema 正确(字段名、类型可能不对)。

2.4.3 Constrained Decoding(最强)

受限解码在解码阶段直接限制 token 的采样空间,保证输出严格符合给定的 JSON Schema。最可靠,但实现复杂、有一定性能开销。

这里重点讲一个实践:为什么”Prompt 约束 JSON 输出”不可靠,以及如何补救。

如果你在 Prompt 里写”请只输出 JSON”,模型大概率会输出 JSON,但”大概率”在工程上是不够的。它可能:

  1. 在 JSON 前后加说明文字(”好的,这是结果:{…}”)。
  2. 输出不合法的 JSON(漏了引号、逗号、括号)。
  3. 输出合法 JSON 但字段名不对、类型不对。

补救方案是**”解析失败就重试”**:

async function getStructured<T>(prompt: string, schema: ZodSchema<T>): Promise<T> {
  for (let attempt = 0; attempt < 3; attempt++) {
    const text = await callModel(prompt);
    try {
      const json = extractJson(text);  <em>// 提取文本中的 JSON 片段</em>
      return schema.parse(json);       <em>// Zod 校验并解析</em>
    } catch (err) {
      prompt += `\n\n你上次的输出格式错误(${err.message}),请重新输出严格的 JSON。`;
    }
  }
  throw new Error("结构化输出失败");
}

这个”生成 → 解析 → 失败反馈 → 重生成”的模式,是 LLM 应用中最常见、也最实用的容错模式。它的思想——把错误信息反馈给模型让它自我纠正——正是后面第 47 章 Self-Correction 的雏形。

更高级的 Structured Output 用 Constrained Decoding。Anthropic 的 Claude 通过 tool_choice 和 structured outputs 特性支持这一点;OpenAI 有 response_format: { type: "json_schema" }。这是生产环境的首选,因为它的可靠性是”结构性保证”而非”概率性希望”。

一个关键的设计选择是:当需要结构化输出时,优先考虑”用一个工具调用”来承载它。因为工具调用本身就是结构化的(工具名 + JSON 参数),很多框架把”结构化输出”实现为”调用一个特殊工具”。这是一种巧妙的复用。


2.5 Function Calling / Tool Calling

Tool Calling(Anthropic 的术语)和 Function Calling(OpenAI 的术语)指的是同一件事:让模型能够请求调用外部函数。这是 Agent 的基石,值得用一整节讲清楚它的完整流程。

Tool Calling 的完整生命周期分为五步:

第一步:定义工具(Tool Definition)

开发者向模型声明有哪些工具可用,每个工具包含名称、描述、参数 Schema:

const tools = [
  {
    name: "get_weather",
    description: "获取指定城市的当前天气",
    input_schema: {
      type: "object",
      properties: {
        city: { type: "string", description: "城市名,如 '北京'" }
      },
      required: ["city"]
    }
  }
];

第二步:模型决策(Tool Decision)

模型在推理过程中,判断需要调用工具,于是不再输出文本,而是输出一个工具调用请求:

{
  "type": "tool_use",
  "id": "toolu_01AbCdEf",
  "name": "get_weather",
  "input": { "city": "北京" }
}

第三步:执行工具(Tool Execution)

Runtime(在 Agent 语境下即 Harness)收到工具调用请求,找到对应的实现函数,执行它:

function getWeather(city: string) {
  return fetchWeatherApi(city); <em>// 真正去查天气</em>
}
const result = getWeather("北京"); <em>// { weather: "晴", temp: 25 }</em>

第四步:返回结果(Tool Result)

Runtime 把执行结果作为一个新消息追加到对话历史:

messages.push({
  role: "user",
  content: [{
    type: "tool_result",
    tool_use_id: "toolu_01AbCdEf",
    content: JSON.stringify({ weather: "晴", temp: 25 })
  }]
});

第五步:模型基于结果继续推理

模型收到工具结果后,结合结果生成最终回答或继续调用下一个工具。

这五步构成一个”回合”。而 Agent Loop,本质就是这个五步回合的循环往复(第 3 章)。

两个容易混淆但必须分清的概念:

  1. 并行工具调用(Parallel Tool Calling):模型可以在一次回复中请求调用多个工具。这些工具如果相互独立,Harness 可以并行执行(第 20 章会讲到 Claude Code 用 partitionToolCalls 区分”可并发的只读工具”和”必须串行的写工具”)。
  2. 强制工具调用(tool_choice):通过 tool_choice 参数,可以强制模型”必须调用某个工具”或”必须调用工具”。这常用于”结构化输出”场景——强制模型用一个工具来返回结构化的结果。

2.6 Tool Schema

Tool Schema(工具模式)是工具对模型暴露的”接口契约”。它的质量直接决定模型能否正确使用工具。设计 Tool Schema 是 Tool System 中最容易被低估、却最影响效果的环节。

一个优秀的 Tool Schema 设计原则:

  1. name 要精确且见名知义:get_weather 好于 tool1;用 snake_case 保持一致性。
  2. description 要详尽:描述工具做什么、什么时候用、有什么限制。模型是”读”这段描述来决定是否调用的,描述越清楚,误用越少。
  3. 参数要有 description:每个参数都要说明含义、格式、取值范围。尤其是单位、时区、枚举值这些容易歧义的地方。
  4. required 要准确:明确哪些参数必填,减少模型漏传或误传。
  5. 不要过度设计:参数太多、描述太啰嗦,反而消耗 token、分散注意力。

一个反面例子:

{ name: "f", description: "do something", input_schema: { type: "object" } }

模型看到这样的工具,根本无法正确使用。一个正面例子:

{
  name: "search_files",
  description: "在项目中搜索包含指定文本的文件。返回匹配的文件路径和行号。用于定位函数定义、变量引用或错误信息。",
  input_schema: {
    type: "object",
    properties: {
      pattern: { type: "string", description: "要搜索的正则表达式模式" },
      directory: { type: "string", description: "搜索目录,默认为项目根目录" },
      file_glob: { type: "string", description: "文件名过滤,如 '*.ts'" }
    },
    required: ["pattern"]
  }
}

工具 Schema 的设计质量,本质上是”API 文档质量”的问题。正如好的 API 文档让开发者少犯错,好的 Tool Schema 让模型少犯错。

一个值得注意的细节:描述要站在”模型”的视角写,而不是”实现者”的视角。实现者知道 search_files 内部怎么实现(用了 ripgrep、递归目录),但模型不需要知道这些。模型需要知道的是”什么时候该用这个工具、传什么参数、会得到什么结果”。


2.7 Tool Result

Tool Result(工具结果)是工具执行后返回给模型的内容。它的设计同样关键,因为模型要基于这个结果继续推理。

Tool Result 的设计原则:

  1. 返回结构化、可解析的内容:JSON 比自由文本更容易被模型理解。
  2. 控制大小:工具结果会进入上下文,占用 token。如果一个工具返回了 10 万 token 的内容,会立刻挤爆上下文。因此工具结果往往需要截断或摘要(第 21 章、第 35 章的 Context Compaction)。
  3. 区分成功与失败:成功返回数据,失败返回清晰的错误信息(含错误类型和原因),让模型能理解发生了什么。
  4. 携带足够的上下文:比如搜索工具不仅要返回”找到 3 个匹配”,还要返回匹配的具体内容和位置,否则模型还得再调一次工具去读文件。

一个关键认知:工具结果的质量,直接决定 Agent 的决策质量。如果工具返回的信息残缺、模糊、噪声大,模型就会被误导,做出错误决策,甚至陷入”反复调用工具却得不到有用信息”的循环。

这个循环有一个专门的术语叫”工具调用卡壳”(Tool-call thrashing)——模型反复调用工具,但每次得到的结果都无法推进任务。要避免它,工具结果必须”一步到位”地提供模型需要的完整信息。


2.8 Error Handling

LLM 应用的错误处理,比传统应用复杂,因为错误来源更多元:

  1. 网络错误:请求超时、连接中断、网关错误。
  2. API 错误:限流(429)、服务过载(529)、鉴权失败(401)、参数错误(400)。
  3. 模型错误:输出格式不符合预期、工具参数非法、内容幻觉。
  4. 工具错误:文件不存在、命令执行失败、外部服务不可用。

错误处理的核心策略是分类处理:

async function callModelWithRetry(request) {
  try {
    return await callModel(request);
  } catch (err) {
    if (isRateLimitError(err)) {
      <em>// 429:等待后重试</em>
      await sleep(err.retryAfterMs ?? 2000);
      return callModelWithRetry(request);
    }
    if (isOverloadError(err)) {
      <em>// 529:服务过载,退避重试</em>
      await sleep(exponentialBackoff());
      return callModelWithRetry(request);
    }
    if (isAuthError(err)) {
      <em>// 401:不可重试,直接抛出</em>
      throw new AuthError("API key 无效");
    }
    throw err;
  }
}

关键原则:区分”可重试错误”和”不可重试错误”。网络抖动、限流、过载是可重试的;鉴权失败、参数错误通常是不可重试的(重试也白费)。

一个更细致的错误分类法(值得记下来):

错误类别是否可重试处理方式
网络超时/中断可重试退避重试
限流 429可重试等待 retry-after 后重试
过载 529可重试指数退避重试
鉴权 401不可重试立即失败,提示用户
参数 400不可重试修复请求后重试
上下文过长特殊压缩上下文后重试
输出超限特殊续写/降级

这个分类法贯穿全书——尤其是 Agent Loop 的健壮性(第 3、12 章)就建立在”正确的错误分类”之上。


2.9 Retry

Retry(重试)是提升 LLM 应用可靠性的基础手段。但重试不是”无脑再来一次”,它需要一套策略:

  1. 指数退避(Exponential Backoff):每次重试的等待时间递增,如 1s → 2s → 4s → 8s,避免在服务过载时雪上加霜。
  2. 抖动(Jitter):在退避时间上加入随机抖动,避免多个客户端同时重试造成”惊群效应”。
  3. 最大重试次数:设置上限(如 3-5 次),避免无限重试。
  4. 幂等性:对于有副作用的操作(如写文件、发消息),重试可能导致重复执行,需要幂等设计。
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === maxRetries - 1 || !isRetryable(err)) throw err;
      const delay = 1000 * 2 ** i + Math.random() * 500; <em>// 指数退避 + 抖动</em>
      await sleep(delay);
    }
  }
  throw new Error("unreachable");
}

一个进阶的话题是熔断器(Circuit Breaker):当连续失败次数超过阈值,就”断开”一段时间,直接快速失败,而不是继续重试。这防止了”雪崩”——当一个下游服务已经宕机时,继续重试只会浪费资源、加剧拥塞。Claude Code 的 autoCompact 里就有类似的熔断逻辑(连续压缩失败就停止重试),第 21 章会看到。


2.10 Timeout

Timeout(超时)是防止 LLM 应用”卡死”的关键机制。模型推理可能非常慢(尤其是推理模型,长任务可能耗时几分钟),如果没有超时控制,应用会一直等待。

超时设计需要分层:

  1. 连接超时:建立连接的等待时间(通常几秒)。
  2. 首 token 超时:等待第一个 token 出现的时间(对流式体验很重要)。
  3. 总超时:整个请求的最大时长。

对于长任务,简单粗暴的总超时会导致误杀。更好的做法是:流式场景下用”首 token 超时 + 空闲超时”——如果长时间没有新 token 到来(说明模型可能卡住了),才判定超时。

<em>// 空闲超时:如果 N 秒内没有新 token,判定超时</em>
let lastTokenTime = Date.now();
stream.on("text", () => { lastTokenTime = Date.now(); });
const idleTimer = setInterval(() => {
  if (Date.now() - lastTokenTime > IDLE_TIMEOUT) {
    controller.abort();
  }
}, 1000);

超时设计的核心矛盾:太短会误杀长任务,太长会浪费资源。解决之道是”分层超时”——不同阶段用不同的超时,而不是一个总超时打天下。


2.11 Model Router

Model Router(模型路由)是在多个模型之间做选择的组件。它的存在源于一个基本事实:没有”万能模型”,不同模型在能力、成本、延迟上各有优劣。

模型路由的常见策略:

  1. 按任务类型路由:简单任务用便宜快速的模型,复杂任务用贵而强的模型。
  2. 按成本路由:预算充足时用最强模型,预算紧张时降级。
  3. 按可用性路由:主模型不可用时自动切换到备用模型(Failover)。
  4. 级联路由(Cascade):先用小模型尝试,若结果不理想(如置信度低、格式错误),再升级到大模型重做。

一个简单的路由器设计:

interface ModelConfig {
  name: string;
  costPerInputToken: number;
  costPerOutputToken: number;
  capability: "basic" | "advanced";
}

class ModelRouter {
  route(task: Task): ModelConfig {
    if (task.requiresAdvancedReasoning) return advancedModel;
    if (task.isSimple) return cheapModel;
    return defaultModel;
  }
}

级联路由(Cascade Routing)值得展开:它的思想是”先用便宜模型试,失败再升级“。因为大部分简单任务,便宜模型就能做好;只有少数难任务才需要强模型。这样,平均成本大幅下降,而能力不降。

模型路由在 Part VI 的”企业级 Agent Platform”中会进一步发展,成为成本控制和能力保障的核心组件。


2.12 Token / Cost Management

Token 和成本管理是 LLM 应用从”demo”走向”生产”必须面对的工程问题。它包含三个方面:

2.12.1 Token 计量

要管理成本,先要能计量。每一轮请求,都要记录:

  • 输入 token 数(含 System Prompt、历史、工具结果)。
  • 输出 token 数。
  • 使用的模型及单价。

大多数 API 会在响应中返回 usage 信息:

{
  usage: {
    input_tokens: 1500,
    output_tokens: 300,
    cache_read_input_tokens: 0,
    cache_creation_input_tokens: 0
  }
}

2.12.2 成本估算

成本 = 输入 token 数 × 输入单价 + 输出 token 数 × 输出单价。不同模型单价差异巨大,输出 token 通常比输入 token 贵数倍。

一个容易被忽视的成本大头是 Agent 的循环成本:一次工具调用就是一轮额外的模型请求。一个 Agent 跑 50 个循环,成本可能是单次问答的 50 倍。这也是为什么”控制循环次数”是 Agent 成本管理的核心。

2.12.3 成本优化

成本优化的手段很多,按效果排序大致是:

  1. 减少冗余上下文:不把无关内容塞进上下文,这是最有效的省钱方式。
  2. 用缓存(Prompt Caching):对重复出现的 System Prompt 和稳定前缀,利用 API 的缓存机制大幅降低输入成本。以 Anthropic 为例,缓存命中的 token 价格约为正常输入价格的 1/10。
  3. 模型分级:简单任务用便宜模型(Model Router)。
  4. 上下文压缩:长对话压缩后再继续(第 35 章)。
  5. 限制 max_tokens:避免模型生成超长无用的输出。

成本管理不是”抠门”,而是一个生产级系统能否长期运行的经济基础。一个没有成本控制意识的 Agent,可能在一个晚上烧掉几千元的 API 费用——这在”Overnight Agent”(第 48 章)场景下尤其危险。


本章小结

本章建立了一个 LLM 应用的基础架构图景,核心要点:

  1. 七大组成:Model、Prompt、Context、Tool、Memory、State、Runtime。其中”模型是无状态的纯函数”是最根本的认知起点。
  2. Chat Completion 是一次性的,Agent 通过 Tool Calling 和循环突破这个限制。
  3. Streaming 是体验关键,但也带来状态管理和工具调用解析的复杂度。
  4. Structured Output 是程序可靠解析模型输出的前提,Prompt 约束不可靠,生产环境应优先用 Constrained Decoding。
  5. Tool Calling 五步生命周期:定义工具 → 模型决策 → 执行工具 → 返回结果 → 继续推理。这是 Agent Loop 的基本单元。
  6. 工程底座:Error Handling(分类处理)、Retry(指数退避+抖动+熔断)、Timeout(分层超时)、Model Router(级联路由)、Token/Cost Management,是所有生产级 LLM 应用绕不开的问题。

有了这些基础,下一章我们聚焦整个 Agent 领域最核心的一段代码——Agent Loop。它只有几十行,却是理解 Agent 与 Harness 分野的钥匙。

更多内容:

《AI Agent 架构设计与工程实践指南》——从 LLM、Agent、Harness 到 Mini Claude Code – 编程开物

Post Views: 1
AI应用架构设计 技术教程 AI AgentAI Agent HarnessAI Agent 工程AI Agent 架构AI 工程AI 应用AI 应用架构AI 智能体AI 智能体架构

文章导航

Previous post
Next post

Comment

  1. Pingback: 《AI Agent 架构设计与工程实践指南》——从 LLM、Agent、Harness 到 Mini Claude Code - 编程开物

发表回复 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注

站内搜索

微信公众号

广告赞助

近期文章

  • Token经济:从10亿亿到3500亿亿,Token正在形成智能经济模型
  • 第 2 章 LLM Application 的基本架构
  • 第 1 章 重新认识 AI Agent
  • 《AI Agent 架构设计与工程实践指南》——从 LLM、Agent、Harness 到 Mini Claude Code
  • 2025年企业级编程技术栈趋势简报
  • 构建高效智能体【译】
  • 软件系统架构演进:单体、微服务和打包业务能力(PBC)
  • 免费HTTPS证书配置 :CentOS 7 + Nginx + Let’s Encrypt 全流程指南
  • AI编程实战001:从0打造网页版打字游戏《快乐打地鼠》
  • 机器学习三要素:模型假设、评价函数与优化算法如何协同工作

近期评论

  • 《AI Agent 架构设计与工程实践指南》——从 LLM、Agent、Harness 到 Mini Claude Code - 编程开物 发表在《第 2 章 LLM Application 的基本架构》
  • 《AI Agent 架构设计与工程实践指南》——从 LLM、Agent、Harness 到 Mini Claude Code - 编程开物 发表在《第 1 章 重新认识 AI Agent》

归档

  • 2026 年 9 月 (4)
  • 2025 年 8 月 (1)
  • 2025 年 7 月 (1)
  • 2025 年 6 月 (10)
  • 2025 年 5 月 (10)
  • 2025 年 4 月 (5)
  • 2025 年 2 月 (1)
  • 2024 年 12 月 (4)
  • 2024 年 11 月 (7)
  • 2024 年 9 月 (1)
  • 2024 年 8 月 (4)
  • 2024 年 7 月 (1)
  • 2024 年 2 月 (1)
  • 2023 年 12 月 (3)
  • 2023 年 11 月 (6)
  • 2023 年 10 月 (4)
  • 2023 年 9 月 (2)
  • 2023 年 8 月 (38)
  • 2022 年 2 月 (1)
  • 2022 年 1 月 (13)
  • 2021 年 1 月 (1)
  • 2020 年 10 月 (1)
  • 2020 年 1 月 (1)
  • 2014 年 7 月 (2)

分类

  • IT咨询 (5)
    • IT咨询框架 (3)
  • IT项目管理 (2)
  • 人工智能 (12)
    • AI概念和理论 (1)
    • 人工智能应用 (2)
    • 数据科学 (3)
  • 企业架构 (8)
    • AI应用架构设计 (3)
    • 传统应用架构设计 (2)
  • 工具Tips (3)
  • 技术教程 (3)
  • 生活笔记 (23)
  • 编程基础 (3)
    • 计算机网络 (2)
  • 编程笔记 (56)
    • .NET技术栈 (3)
    • C语言编程 (1)
    • Golang技术栈 (1)
    • iOS App开发 (1)
    • Python编程 (18)
    • UE虚幻引擎 (1)
    • Unity游戏开发 (9)
    • Wordpress (5)
    • 工具 (1)
  • 行业动态 (16)
©2026 编程开物 | WordPress Theme by SuperbThemes | 沪ICP备17019044号-3