site logo

Marico's space

AI 应用工程:从 LLM API 到 Agent、Harness 与生产系统

AI技术与应用 2026-10-14 14:50:43 12

最近折腾了几个 AI 项目,踩了不少坑,这篇把从 LLM API 到 Agent、Harness 再到生产系统的东西捋清楚。

把大模型接进应用里其实不难,发个请求、收个响应、显示出来就完事了。但要做成一个可靠的应用,那就是另一回事了。

真实的应用需要管理上下文、执行工具、保持状态、处理中断、请求审批、处理异常、恢复故障,还得把进度反馈给用户。有些应用还要处理图片、音频、视频、文档、结构化数据或者实时事件。

需求一多,模型就成了整个系统里的一个组件,而不是全部。

这篇文章聊一聊 AI 应用工程的完整图景:从模型能力、SDK 集成,到 Agent 循环、上下文管理、Harness 工程,再到生产级运维。

会拿 Vercel AI SDK 写点 TypeScript 示例,其他厂商的模型(OpenAI、Anthropic 等)思路也是相通的。

目标不是把 API 清单背一遍,是搞清楚这些组件怎么配合、各自解决什么问题、职责边界在哪里。

1. AI 应用的技术栈

先建个 mental model。

一个生产级的 AI 应用可以理解成几层协作的结构。

1.1 模型层

模型提供的能力包括:

  • 文本生成和摘要
  • 推理和问题求解
  • 图片和文档理解
  • 音频理解和生成
  • 结构化输出
  • 工具选择
  • 嵌入向量和语义表示
  • 视频理解(部分模型支持)

不是每个模型都支持所有能力,可用性、质量、限制和定价取决于具体的模型和提供商。

1.2 API 和 SDK 层

厂商 API 通过网络接口暴露模型能力。SDK 则在这个接口上封装了一层,提供类型化的函数、请求构造器、流式响应辅助工具和厂商特定 abstractions。

1.3 应用运行时

运行时控制模型周围发生的事情:

  • 构造 Prompt 和上下文
  • 选择可用工具
  • 执行工具调用
  • 管理 Agent 循环
  • 应用授权规则
  • 处理 Hooks 和中间件
  • 维护状态
  • 处理异常和重试
  • 协调长时任务

1.4 体验层

用户界面把运行时的行为传达给用户:

  • 流式文本
  • 工具调用进度
  • 结构化结果
  • 交互组件
  • 澄清问题
  • 审批请求
  • 任务状态和错误

1.5 运维和治理

生产系统还需要:

  • 评估和回归测试
  • 可观测性和链路追踪
  • 成本和延迟控制
  • 数据隐私和保留策略
  • 安全边界
  • 人工监督
  • 恢复和审计能力

一个有用的原则是:模型提议,运行时协调,工具执行工作,应用强制执行策略。

这个分离在 AI 应用越来越自主化的趋势下会变得越来越重要。

2. LLM 在应用里到底是怎么跑的

不需要把 Transformer 架构的每个细节都搞懂,但得理解那些影响系统设计的概念。

2.1 模型和推理

模型根据输入和它学到的参数生成输出。在应用里,把模型跑起来产生输出的过程叫推理(Inference)。

输入可能包含指令、对话消息、检索到的文档、图片、音频、工具定义或者结构化数据。

输出可能是纯文本、结构化内容、语音回复,或者是调用工具的请求。

模型不会自动访问你的数据库、文件系统、内部 API 或者业务系统。这些能力必须通过合适的集成来暴露。

2.2 Token 和上下文窗口

模型通过底层表示来处理文本和其他内容。对于文本,通常用 Token 而不是词或字符来衡量。

Token 影响:

  • 上下文窗口容量
  • 输入输出的成本
  • 生成响应的耗时
  • 能塞进多少对话历史
  • 一次请求里能放多少工具定义和检索文档

上下文窗口是模型一次请求能处理的最大上下文量。具体限制和计费规则因模型而异。

更大的上下文窗口不自动意味着更好的效果。不相关或者矛盾的信息可能让请求效果变差,即使它还能塞进容量里。

2.3 消息和角色

很多对话 API 把交互表示为带角色的消息。

常见角色包括:

  • System 或 Developer 指令: 建立行为规范、约束和应用级规则。
  • User: 包含用户的请求和提供的信息。
  • Assistant: 包含模型的回复,或者是调用工具的请求。
  • Tool Result: 包含外部操作返回的信息。

具体消息结构在不同厂商间有差异。有些 API 还支持更丰富的内容块来处理图片、音频、推理相关数据和工具交互。

对话不一定只是一串纯文本消息的数组,它可以是不同类型内容和事件的序列。

2.4 推理和扩展思考

有些模型支持推理导向的能力或可配置的思考模式,能在复杂任务上提升表现。

这些对编码、规划、分析和多用步骤工具调用很有用。但推理能力在不同模型和厂商间差异很大。

应用开发者应该区分:

  • 模型的内部计算
  • API 暴露的推理相关内容
  • 面向用户的解释
  • 工具实际执行的操作

不要以为暴露内部推理过程是必须的。一个好用的应用可以展示简洁的解释、来源、行动历史和可验证的结果。

推理也有实际成本:更多的计算通常意味着更高的延迟和 Token 消耗。

3. 不只是文字聊天的 AI 能力

最重要的一点认知是:AI 不等于在聊天窗口里生成文字。

现代 AI API 暴露了多种能力,一个应用可以在一个工作流里组合使用它们。

3.1 文本生成

文本生成是我们熟悉的聊天助手、摘要、改写、翻译、分类、内容生成背后的能力。

典型场景:

  • 回答问题
  • 摘要报告
  • 起草邮件
  • 解释代码
  • 从文档中提取信息
  • 生成上下文推荐

输出可以流式输出,也可以等生成完再返回。

3.2 结构化输出

有时候应用需要的是可预测的数据结构,而不是自由格式的文本。

比如一个任务分类器可能需要返回:

{ "category": "billing", "priority": "high", "requiresHumanReview": true
}

结构化输出功能让开发者用 Schema 或支持的结构化生成机制来描述期望的格式。

这对以下场景很有用:

  • 分类
  • 数据提取
  • 表单填充
  • 报告生成
  • 工作流路由
  • 生成带类型的 UI 数据

Schema 约束的输出能减少格式错误,但不保证值是正确的或操作是安全的。要独立验证结果并强制执行业务规则。

3.3 视觉和图片理解

有视觉能力的模型可以分析通过支持 API 传入的图片。

可能的应用场景:

  • 理解截图
  • 从图表中提取信息
  • 分析产品图片
  • 解读图表
  • 读取支持的文档图片
  • 帮助用户导航视觉界面

图片支持在格式、分辨率、细节处理和 Token 计费上各有差异。

文档理解可能还需要 OCR、布局分析、PDF 解析或专门的文档处理管道。模型能理解一张图片不意味着它能完美提取每个字符或表格。

3.4 音频理解和生成

音频能力可以支持:

  • 语音转文字转录
  • 音频分类
  • 语音问答
  • 文字转语音生成
  • 语音助手
  • 语音摘要
  • 音频翻译(部分支持)

一个简单的语音助手可能用这样的管道:

  1. 采集音频。
  2. 转录语音或直接把音频发给能处理的模型。
  3. 理解请求。
  4. 必要时调用工具。
  5. 生成回复。
  6. 把回复转成语音。
  7. 播放给用户。

其他架构用能同时处理多个阶段的语音模型。

这些设计在延迟、成本和交互特性上各有不同。实时语音应用可能还需要打断处理、轮次检测、流式音频和会话管理。

3.5 视频和实时感知

视频理解可以通过支持的视频输入、采样帧、音频轨道或模型特定的视频接口来构建。

可能的应用场景:

  • 总结录制的会议
  • 理解演示内容
  • 分析事件序列
  • 从培训视频中提取信息
  • 解读时间跨度内的视觉变化

开发者需要考虑帧采样、时间戳、音频同步、数据量和处理成本。模型收到的是选定的帧,不一定理解视频的每个瞬间。

3.6 嵌入向量和语义相似度

嵌入向量把内容表示成可以数学比较的数值向量。

它们对以下场景有用:

  • 语义搜索
  • 文档检索
  • 相似度检测
  • 推荐系统
  • 聚类
  • RAG(检索增强生成)

比如两篇文档讨论同一个概念但用了不同的词汇,语义搜索系统仍然能检索到相关内容,即使查询没有共享精确的关键词。

嵌入向量不是答案本身。它们是被检索和相似度系统使用的表示。

3.7 工具使用和外部操作

模型可以请求暴露以下能力的工具:

  • 搜索内部文档
  • 查询数据库
  • 读取文件
  • 调用业务 API
  • 创建工单
  • 运行支持的计算
  • 搜索网页

模型通常提出工具调用并提供参数。应用或提供商管理的执行环境决定实际运行什么。

这个区别很关键:工具调用不仅仅是生成的文本,描述一个动作不等于执行它。

3.8 计算机使用和浏览器交互

有些系统支持计算机使用或浏览器使用能力,让 Agent 能通过截图、点击、输入或其他操作与支持的界面交互。

这些能力可以实现:

  • 基于浏览器的调研
  • 与遗留系统交互
  • 重复性 UI 工作流
  • 测试用户旅程
  • 处理没有合适 API 的应用的任务

计算机使用引入了额外的风险。Agent 可能误解屏幕内容、点错控件或遇到不受信任的内容。敏感操作需要强健的执行边界、验证和适当的人工监督。

如果存在稳定的 API,通常比通过 UI 控制同一系统更可靠。

3.9 模型能力与应用功能的对比

按职责归属来分类功能很有帮助。

能力 主要职责
文本、视觉、音频、支持的视频 模型和提供商
结构化生成 模型/API,应用需验证
嵌入向量 嵌入模型和检索基础设施
流式响应 API 传输和 SDK
工具选择 模型引导行为,取决于 API
工具执行 应用运行时或提供商管理的执行
记忆 应用架构和存储
RAG 检索管道和应用逻辑
审批 应用授权和用户体验
Hooks 和中间件 运行时或框架生命周期
压缩 上下文管理策略
UI 渲染 前端应用
评估和追踪 应用运维和评估系统

不是每个提供商都支持每个功能,而且名称相似的功能可能有不同的语义。

4. Prompt 工程 vs 上下文工程

这两个术语紧密相关,但解决的问题不同。

4.1 Prompt 工程

Prompt 工程关注的是给模型的指令和框架。

一个 Prompt 可能定义:

  • 任务
  • 期望的语气
  • 输出格式
  • 约束条件
  • 示例
  • 决策标准
  • 处理不确定性的规则

比如:

You are a support assistant. Classify the incoming request into one of:
- billing
- technical_support
- account_access Return only the requested structured result.
If the information is insufficient, mark the result as uncertain.

这告诉模型怎么处理这个任务。

4.2 上下文工程

上下文工程关注的是在特定时刻选择和组织哪些信息供模型使用。

可能包括:

  • System 和 Developer 指令
  • 最近的对话消息
  • 用户偏好
  • 检索到的文档
  • 工具定义
  • 工具结果
  • 当前工作流状态
  • 相关的历史决策
  • 输出约束和 Token 预算

核心问题不只是"应该告诉模型什么",还有"对于这个具体决策,什么信息应该是可用的"。

模型可能有很好的指令,但如果收到过期数据、不相关文档、缺失的权限或矛盾的对话历史,仍然可能产生糟糕的结果。

4.3 上下文组装

实际应用通常在推理前有一个上下文构建阶段。

比如:

Incoming request | v
Identify task and permissions | v
Load relevant session state | v
Retrieve relevant information | v
Select available tools | v
Assemble model context | v
Run inference

这个过程应该是审慎的。默认把每个可用的文档、工具定义和之前的消息都塞给每次模型调用,通常不是好做法。

4.4 上下文污染

上下文污染发生在不相关、冗余、过时或矛盾的信息消耗了注意力或容量时。

常见原因包括:

  • 重复的指令
  • 不再有用的工具输出
  • 不相关的对话历史
  • 过多的工具定义
  • 相关性弱的检索文档
  • 与当前状态矛盾的老摘要

更多上下文不一定更好。目标是提供对当前任务最有用的上下文。

4.5 Prompt 缓存

Prompt 缓存让支持的提供商能复用符合条件的重复 Prompt 前缀的处理。

在合适的工作负载中能降低延迟和输入处理成本,尤其是稳定的指令或工具定义被复用时。

但 Prompt 缓存不等同于对话记忆或上下文压缩:

  • Prompt 缓存复用符合条件的重复输入处理。
  • 记忆在交互间保留信息。
  • 压缩减少携带到下一步的上下文。
  • 检索选择与当前请求相关的信息。

这些机制可以互补。

5. 使用 AI SDK 和提供商 API

SDK 简化了集成,但不等于不需要理解底层模型和 API 行为。

5.1 提供商 SDK

特定于提供商的 SDK 通常暴露特定提供商的能力和约定。

它们可能提供对提供商特定请求参数、响应格式、流式事件和专业能力的直接访问。

当应用需要精确控制某个提供商的 API 时很有用。

5.2 统一 AI SDK

统一的 SDK 可以为多个提供商提供通用接口。

比如 Vercel AI SDK 提供了文本生成、流式响应、工具调用、结构化输出、嵌入向量和其他支持的 AI 任务的函数。

一个简单的 TypeScript 示例:

import { generateText } from "ai";
import { openai } from "@ai-sdk/openai"; const result = await generateText({ model: openai("gpt-4.1"), prompt: "Explain context engineering in simple terms.",
}); console.log(result.text);

这展示了非流式文本生成的请求。模型标识符是示例,选择你账户可用的、支持所需操作的模型。

SDK 提供了方便的接口,但你的应用仍然需要处理认证、错误、超时、成本控制和合适的数据处理。

5.3 流式响应

对于交互式应用,等整个响应返回才显示体验很差。

流式响应让应用能增量消费输出。

import { streamText } from "ai";
import { openai } from "@ai-sdk/openai"; const result = streamText({ model: openai("gpt-4.1"), prompt: "Explain how an AI agent uses tools.",
}); for await (const chunk of result.textStream) { process.stdout.write(chunk);
}

这个示例把文本流式输出到服务器端。Web 应用通常会用合适的响应格式把流发送到前端。

流式响应能改善感知到的响应速度,但不一定会减少产生最终响应所需的总计算量。

另外,不是每个流式事件都是面向用户的文本。工具调用、结构化数据、推理相关内容、状态事件可能需要不同的处理。

5.4 SDK abstractions 不是通用契约

两个 SDK 可能暴露名称相似但语义不同的概念。

比如:

  • 一个框架里的生命周期回调可能与另一个框架的 hook 行为不同。
  • 一种配置下工具调用可能自动执行,另一种配置下可能需要应用处理。
  • 结构化输出约束可能因提供商而异。
  • 流式事件和中断行为可能不完全一致。

把 SDK abstractions 当作实现接口,而不是证明每个提供商行为相同的证据。

6. 工具调用:把模型连接到真实系统

工具调用是 AI 应用工程中最重要的概念之一。

工具暴露一个定义好的操作,模型可以请求这个操作。应用决定如何验证、授权和执行这个请求。

6.1 定义一个工具

一个工具通常包含:

  • 名称
  • 描述
  • 输入 Schema
  • 执行处理器(如果执行发生在当前运行时)
  • 输出或结果表示

比如,一个客服助手可能有一个 get_order_status 工具。

模型可能请求:

{ "orderId": "ORD-12345"
}

应用验证参数、检查用户是否可以访问那个订单、查询相应的服务、返回结果。

模型然后可以用这个结果来组织答案。

6.2 工具调用 vs 工具执行

这是分开的阶段:

  1. 模型请求工具。
  2. 运行时收到请求。
  3. 运行时验证参数。
  4. 运行时检查权限和策略。
  5. 如果获得授权,工具执行。
  6. 运行时捕获结果。
  7. 结果提供给模型或呈现给用户。
  8. 模型可能继续或结束。

有些 API 和 SDK 把这个过程的部分自动化了。其他的把这些留给应用处理。

关键点是应用必须知道哪个组件负责执行。

6.3 一个实际的工具示例

import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod"; const result = await generateText({ model: openai("gpt-4.1"), prompt: "Find the status of order ORD-12345.", tools: { getOrderStatus: tool({ description: "Retrieve the status of an order.", inputSchema: z.object({ orderId: z.string(), }), execute: async ({ orderId }) => { // Authenticate and authorize the request in a real application. // Query the order service here. return { orderId, status: "shipped", }; }, }), },
}); console.log(result.text);

这是说明性的集成模式,不是完整的生产订单服务。示例处理器返回静态结果。真实实现需要验证访问、处理服务错误,并且在没有授权检查的情况下不要盲目信任模型提供的订单标识符。

6.4 工具路由和发现

随着工具数量增长,决定暴露哪些工具就成了架构问题。

可能的策略:

  • 暴露小型固定的工具集
  • 根据用户任务选择工具
  • 按需加载专用工具
  • 按领域分组工具
  • 用路由器识别相关子系统
  • 根据用户权限限制工具

比如,一个开发者助手可能有访问 GitHub、工单跟踪、内部文档和部署系统的权限。关于 PR 的问题不一定需要加载所有部署和文档工具。

动态发现能减少上下文使用,但发现机制本身需要授权和可观测性。

6.5 并行工具调用

有些任务通过并发运行独立操作能完成得更快。

比如,助手可能同时获取文档和工单元数据。

并行执行需要注意:

  • 速率限制
  • 资源限制
  • 操作间的依赖
  • 错误处理
  • 部分结果
  • 副作用

独立的读操作通常是并行的好候选。有冲突的写操作或有依赖的操作需要更仔细的协调。

7. Agent 循环、工作流和编排

仅靠工具调用不能让一个应用成为完整的 Agent 系统。

一个 Agent 工作流通常涉及重复的决策和操作,直到达到目标或系统确定无法继续。

7.1 Agent 循环

简化的循环看起来是这样的:

Receive goal | v
Assemble context | v
Ask model for next step | v
Tool call requested? / \ No Yes
 | |
v v Finish Validate and authorize | v Execute tool | v Record result | +------> Continue loop

循环必须有终止条件。否则 Agent 可能重复调用工具、消耗资源或不取得进展。

有用的控制包括:

  • 最大步数
  • 截止时间
  • Token 和成本预算
  • 工具执行超时
  • 重试限制
  • 取消
  • 进度检查

7.2 工作流 vs Agent

工作流遵循预定义的步骤序列。

比如:

Receive support request | v
Classify request | v
Retrieve account information | v
Draft response | v
Human review | v
Send response

Agent 在选择下一步时有更大的灵活性,基于当前上下文和之前操作的结果。

工作流适用于流程事先已知的情况。Agent 适用于路径取决于执行中发现的信息的情况。

很多生产系统把两者结合:确定性工作流建立边界,Agent 处理选定的推理和工具使用步骤。

7.3 编排

编排协调多个步骤或组件的执行。

它可能处理:

  • 步骤排序
  • 并行执行
  • 重试
  • 分支
  • 超时
  • 状态转换
  • 人工打断
  • 故障恢复

编排不等于多 Agent 架构。单个 Agent 可以通过复杂的工作流进行编排,多个 Agent 也可以在相对简单的编排系统内运行。

7.4 多 Agent 系统

多 Agent 系统把职责分配给多个模型驱动的组件。

比如:

  • 研究 Agent 收集证据。
  • 分析 Agent 评估证据。
  • 编码 Agent 准备变更。
  • 审查 Agent 检查提议的结果。

当任务有不同上下文、能力或职责时这很有帮助。

然而,多个 Agent 也引入了协调开销、更多推理成本、更多故障模式和可能的分歧。应该在专业化带来可衡量优势时才使用,而不是仅仅因为架构允许。

8. Hooks、中间件和运行时生命周期

Hooks 和中间件在 AI 工程中经常被提到,但含义因框架而异。

一个有用的理解方式是考虑 AI 请求的生命周期。

8.1 什么是 Hooks

Hook 让运行时或应用能把自定义行为附加到定义的 lifecycle 事件上。

根据框架,相关事件可能包括:

  • 模型请求前
  • 模型响应后
  • 工具执行前
  • 工具执行后
  • Agent 启动或结束时
  • 任务失败时
  • 需要用户审批时

Hooks 可用于日志、验证、指标、策略检查和其他横切行为。

具体的 API 和执行语义是框架特定的。AI SDK 里的 hook、编排框架里的生命周期回调和编码 Agent 运行时里的 hook 不能自动互换。

8.2 什么是中间件

中间件包装或拦截操作以应用可复用的行为。

例子包括:

  • 请求日志
  • 认证
  • 输入验证
  • 错误转换
  • 速率限制
  • 模型配置
  • 输出处理

当相同行为必须一致地应用于许多请求时,中间件通常很有用。

8.3 Hooks vs 工具

工具是模型可以请求的操作。

Hook 是与应用或运行时事件关联的逻辑。

比如:

  • searchDocumentation 是一个工具。
  • 记录每次文档搜索是一个生命周期关注点。
  • 检查当前用户是否可以访问文档是一个授权关注点。
  • 拒绝禁止操作是一个策略执行关注点。

Hook 可能调用应用逻辑,但不应该与模型选择的工具混淆。

8.4 Hooks 不是自动的安全边界

生命周期回调只有在可靠地在相关执行边界运行且不能被其他路径绕过时才对安全有用。

对于敏感操作,授权应该由实际的服务或执行层强制执行。UI 限制或 Prompt 指令本身是不够的。

9. 人在回路:审批、interrupt 和澄清

自主性不总是正确的行为。

有些操作应该要求明确的用户授权,有些任务在应用获得额外信息前无法继续。

9.1 审批

审批在提议的操作继续前引入一个决策点。

例子包括:

  • 发送外部邮件
  • 删除记录
  • 创建生产部署
  • 更新财务或账户信息
  • 发布内容
  • 运行破坏性命令

安全的执行流程:

Model proposes action | v
Runtime validates arguments | v
Policy checks required
authorization | v
Approval required? / \ No Yes
 | |
| v | Pause execution
 | |
| v | Present details
 | |
| v | User approves? | / \ | No Yes
 | | |
v v v Execute or reject action

审批不仅仅是 Prompt 指令让模型小心。

运行时必须实际阻塞执行,直到收到合适的授权。

审批记录理想情况下应该标识提议的操作、参数、相关用户和决策结果。如果审批后操作有变更,系统应该再次验证。

9.2 中断

中断暂停工作流,因为需要外部的东西才能继续。

原因可能是:

  • 用户审批
  • 缺失信息
  • 依赖不可用
  • 需要的外部事件
  • 人工审查步骤

中断应该保留足够的状态以便安全恢复,而不是强制整个任务重新开始。

9.3 向用户提问

有时候模型需要在做有用决策前澄清。

比如用户可能说:

安排下周和团队的会议。

应用可能需要知道是哪个团队、哪一天、会议持续多久。

好的系统应该只问继续所需的问题。还应该保留答案,这样用户不需要重复。

澄清和审批是不同的:

  • 澄清收集缺失的信息。
  • 审批授权提议的操作。
  • 认证建立身份