云计算百科
云计算领域专业知识百科平台

五:Agent 可观测性与成本优化——从“能跑“到“好管“

系列第五篇:Agent 可观测性与成本优化——从"能跑"到"好管"

前言

你的 Agent 上线了,评估通过了,记忆也有了。但运维团队很快就会发现一系列棘手问题:

  • 某次请求为什么耗时 25 秒?卡在哪一步了?
  • 这个月 Token 费用为什么突然翻倍?
  • 用户投诉 Agent 回答错了,怎么复现和定位?
  • 某个工具的调用频率为什么异常高?

这些问题的本质都是 可观测性(Observability) 的缺失。本文将系统性地构建 Agent 的可观测性体系,并在此基础上实现精细化的成本优化。


一、Agent 可观测性的特殊性

1.1 为什么传统 APM 不够用?

传统的 APM(Application Performance Monitoring)工具——如 SkyWalking、Zipkin——是为确定性系统设计的。一个 HTTP 请求进来,经过 N 个微服务,每个服务有明确的输入输出,调用链路是确定的。

但 Agent 系统有三个根本不同:

差异一:执行路径不确定

同一个用户问题,Agent 可能调用 2 个工具,也可能调用 5 个工具。每次执行的 ReAct 循环次数、工具调用顺序都可能不同。传统的固定链路追踪无法覆盖。

差异二:中间状态是"文本"而非"结构化数据"

Agent 的每一步推理产出的是自然语言文本(Thought),而非传统的结构化返回值。这些 Thought 对调试至关重要——你需要知道"Agent 为什么做了这个决策"。

差异三:成本模型不同

传统服务的成本主要是 CPU 和内存。Agent 的主要成本是 Token 消耗——按调用量计费,且不同模型、不同步骤的成本差异巨大。一次 Tool Calling 循环可能消耗 2000 Token,也可能消耗 8000 Token。

1.2 Agent 可观测性的五大支柱

┌────────────────────────────────────────────────────────┐
│ Agent 可观测性五大支柱 │
│ │
│ 1. 链路追踪 (Tracing) │
│ 完整记录 ReAct 循环的每一步:推理、工具调用、结果 │
│ │
│ 2. 指标监控 (Metrics) │
│ Token 消耗、延迟分布、工具调用频率、错误率 │
│ │
│ 3. 结构化日志 (Structured Logging) │
│ 每步推理的 Thought、输入输出、模型参数 │
│ │
│ 4. 会话回放 (Session Replay) │
│ 完整重现一次对话的全部过程(调试利器) │
│ │
│ 5. 告警体系 (Alerting) │
│ 成本超标、延迟异常、错误率飙升的实时告警 │
│ │
└────────────────────────────────────────────────────────┘


二、链路追踪:Agent 执行过程的"黑匣子"

2.1 追踪模型设计

Agent 的追踪结构是一棵树,而非传统的线性链路:

Trace: user-query-12345

├── Span: llm-call-1 (意图理解)
│ ├── Input: "分析C栋近一周能耗,异常就通知张工"
│ ├── Output: Thought + Tool Call
│ ├── Tokens: prompt=450, completion=120
│ └── Duration: 1.2s

├── Span: tool-call-1 (query_energy_trend)
│ ├── Input: {areaName: "C栋", days: 7}
│ ├── Output: "平均8734kWh,超出均值84%"
│ └── Duration: 0.3s

├── Span: llm-call-2 (推理:判断是否异常)
│ ├── Input: 工具结果 + 历史消息
│ ├── Output: Thought + Tool Call
│ ├── Tokens: prompt=680, completion=95
│ └── Duration: 0.8s

├── Span: tool-call-2 (send_alert)
│ ├── Input: {recipient: "张工", message: "…"}
│ ├── Output: "发送成功"
│ └── Duration: 0.1s

└── Span: llm-call-3 (最终回答生成)
├── Input: 全部上下文
├── Output: "C栋近7天能耗异常,已通知张工"
├── Tokens: prompt=820, completion=85
└── Duration: 0.9s

2.2 追踪拦截器实现

/**
* Agent 链路追踪拦截器
*
* 实现原理:
* 通过自定义 ChatModelWrapper 拦截每一次 LLM 调用,
* 通过 AOP 拦截每一次 Tool 调用,
* 统一记录到追踪系统中。
*
* 设计原则:
* – 追踪开销 < 5%(异步写入,不阻塞主流程)
* – 追踪数据与业务数据隔离(独立的追踪存储)
* – 支持采样率控制(生产环境不必 100% 追踪)
*/

@Component
public class AgentTracingInterceptor {

private final TraceRepository traceRepo;
private final TracingConfig tracingConfig;

/**
* 拦截 LLM 调用
*
* 记录:输入 Prompt、模型输出、Token 消耗、耗时
*/

public Response<AiMessage> traceLlmCall(
String traceId,
int callIndex,
ChatLanguageModel model,
List<ChatMessage> messages) {

// 采样率检查
if (!shouldTrace()) {
return model.generate(messages);
}

String spanId = UUID.randomUUID().toString().substring(0, 8);
long startTime = System.nanoTime();

// 记录输入
LlmCallSpan span = LlmCallSpan.builder()
.traceId(traceId)
.spanId(spanId)
.callIndex(callIndex)
.inputMessages(messages)
.startTime(Instant.now())
.build();

Response<AiMessage> response;
try {
response = model.generate(messages);

long durationNanos = System.nanoTime() startTime;

// 记录输出和指标
span.setOutputMessage(response.content());
span.setDurationMs(durationNanos / 1_000_000);
span.setPromptTokens(response.tokenUsage().inputTokenCount());
span.setCompletionTokens(response.tokenUsage().outputTokenCount());
span.setStatus("success");

} catch (Exception e) {
span.setStatus("error");
span.setErrorMessage(e.getMessage());
throw e;

} finally {
// 异步写入追踪存储
traceRepo.saveAsync(span);
}

return response;
}

/**
* 采样率控制
*
* 生产环境建议:
* – 10% 的请求做完整追踪(足够发现模式性问题)
* – 100% 的错误请求追踪(不放过任何异常)
*/

private boolean shouldTrace() {
return ThreadLocalRandom.current().nextDouble()
< tracingConfig.getSampleRate();
}
}

2.3 追踪数据模型

/**
* 完整的 Agent 执行追踪记录
*
* 用途:
* 1. 调试:复现某次对话的完整执行过程
* 2. 分析:发现常见的执行模式和异常模式
* 3. 优化:定位耗时最长/成本最高的步骤
*/

public class AgentTrace {
private String traceId; // 全局唯一追踪 ID
private String userId; // 用户 ID
private String sessionId; // 会话 ID
private String userQuery; // 原始用户问题
private Instant startTime;

private List<LlmCallSpan> llmCalls; // LLM 调用链
private List<ToolCallSpan> toolCalls; // 工具调用链
private String finalResponse; // 最终回复

// 汇总指标
private int totalLlmCalls; // LLM 调用总次数
private int totalToolCalls; // 工具调用总次数
private int totalPromptTokens; // Prompt Token 总量
private int totalCompletionTokens; // Completion Token 总量
private long totalDurationMs; // 端到端总耗时

/**
* 生成执行摘要(用于监控面板展示)
*/

public String toSummary() {
return String.format("""
Trace %s | 用户: %s | 问题: %s
LLM调用: %d次 | 工具调用: %d次
Token: %d(prompt) + %d(completion) = %d
总耗时: %dms
工具链: %s
"""
,
traceId, userId, userQuery,
totalLlmCalls, totalToolCalls,
totalPromptTokens, totalCompletionTokens,
totalPromptTokens + totalCompletionTokens,
totalDurationMs,
toolCalls.stream()
.map(ToolCallSpan::getToolName)
.collect(Collectors.joining(" → "))
);
}
}


三、成本精细化监控

3.1 Token 成本模型

不同模型、不同类型的 Token 价格差异巨大。精细化的成本核算需要考虑每一个环节:

/**
* Token 成本计算器
*
* 不同模型的成本结构(以 2026 年价格为参考):
* – GPT-4o: Input $2.50/1M, Output $10.00/1M
* – GPT-4o-mini: Input $0.15/1M, Output $0.60/1M
* – Embedding: $0.02/1M
*
* Agent 场景的特殊性:
* 一次用户对话可能触发 3~5 次 LLM 调用(ReAct 循环),
* 实际成本远超"一问一答"的预期。
*/

@Component
public class TokenCostCalculator {

/** 模型价格配置(可动态更新) */
private final ModelPricingConfig pricingConfig;

/**
* 计算单次对话的总成本
*/

public CostBreakdown calculate(AgentTrace trace) {
double llmCost = 0;
double embeddingCost = 0;
int llmTokens = 0;
int embeddingTokens = 0;

// LLM 调用成本
for (LlmCallSpan span : trace.getLlmCalls()) {
ModelPrice price = pricingConfig.getPrice(
span.getModelName());
llmCost += span.getPromptTokens()
* price.inputPricePerToken();
llmCost += span.getCompletionTokens()
* price.outputPricePerToken();
llmTokens += span.getPromptTokens()
+ span.getCompletionTokens();
}

// Embedding 成本(RAG 检索)
if (trace.getEmbeddingTokens() > 0) {
ModelPrice embPrice = pricingConfig.getPrice("embedding");
embeddingCost = trace.getEmbeddingTokens()
* embPrice.inputPricePerToken();
embeddingTokens = trace.getEmbeddingTokens();
}

return new CostBreakdown(
llmCost, embeddingCost,
llmTokens, embeddingTokens,
llmCost + embeddingCost
);
}
}

public record CostBreakdown(
double llmCostUsd,
double embeddingCostUsd,
int llmTokens,
int embeddingTokens,
double totalCostUsd
) {}

3.2 多维度成本看板数据

/**
* 成本监控服务
*
* 提供多维度的成本聚合数据,用于监控看板展示
*/

@Service
public class CostMonitoringService {

private final TraceRepository traceRepo;
private final TokenCostCalculator costCalc;

/**
* 按用户维度聚合成本
*
* 用途:识别高成本用户,优化资源分配
*/

public Map<String, UserCostSummary> costByUser(
LocalDate start, LocalDate end) {
List<AgentTrace> traces = traceRepo
.findByDateRange(start, end);

return traces.stream()
.collect(Collectors.groupingBy(AgentTrace::getUserId))
.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> {
List<AgentTrace> userTraces = e.getValue();
double totalCost = userTraces.stream()
.mapToDouble(t -> costCalc.calculate(t)
.totalCostUsd())
.sum();
return new UserCostSummary(
e.getKey(),
userTraces.size(), // 对话次数
totalCost,
totalCost / userTraces.size() // 平均每次成本
);
}
));
}

/**
* 按工具维度聚合 Token 消耗
*
* 用途:识别"Token 黑洞"——哪些工具调用后的 LLM 推理
* 消耗了最多 Token(通常是因为工具返回了过长的数据)
*/

public Map<String, ToolTokenSummary> tokenByTool(
LocalDate start, LocalDate end) {
List<AgentTrace> traces = traceRepo
.findByDateRange(start, end);

Map<String, List<Integer>> toolTokens = new HashMap<>();
for (AgentTrace trace : traces) {
for (ToolCallSpan tool : trace.getToolCalls()) {
toolTokens
.computeIfAbsent(tool.getToolName(),
k -> new ArrayList<>())
.add(tool.getResultTokenEstimate());
}
}

return toolTokens.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> new ToolTokenSummary(
e.getValue().size(),
e.getValue().stream()
.mapToInt(Integer::intValue).average().orElse(0),
e.getValue().stream()
.mapToInt(Integer::intValue).sum()
)
));
}

/**
* 成本异常检测
*
* 检测逻辑:
* 1. 单次对话成本超过阈值(如 $0.10)
* 2. 日成本环比增长超过 30%
* 3. 某用户的平均成本突然偏离基线
*/

public List<CostAlert> detectAnomalies(LocalDate date) {
List<CostAlert> alerts = new ArrayList<>();

// 检测1:单次高成本
List<AgentTrace> traces = traceRepo.findByDate(date);
for (AgentTrace trace : traces) {
double cost = costCalc.calculate(trace).totalCostUsd();
if (cost > 0.10) {
alerts.add(new CostAlert(
"HIGH_SINGLE_COST",
String.format("单次对话成本 $%.4f 超过阈值 $0.10",
cost),
trace.getTraceId(),
AlertLevel.WARNING
));
}
}

// 检测2:日成本环比
double todayCost = traces.stream()
.mapToDouble(t -> costCalc.calculate(t).totalCostUsd())
.sum();
double yesterdayCost = getYesterdayTotalCost();
if (yesterdayCost > 0
&& (todayCost yesterdayCost) / yesterdayCost > 0.30) {
alerts.add(new CostAlert(
"DAILY_COST_SPIKE",
String.format("今日成本 $%.2f 较昨日增长 %.0f%%",
todayCost,
(todayCost yesterdayCost) / yesterdayCost * 100),
null,
AlertLevel.CRITICAL
));
}

return alerts;
}
}


四、会话回放:完整的调试利器

4.1 为什么需要会话回放?

当用户投诉"Agent 回答错了"时,你需要知道:

  • Agent 当时看到了什么上下文?
  • 它调用了哪些工具?参数是什么?
  • 工具返回了什么?
  • Agent 在每一步是怎么推理的?
  • 最终为什么给出了错误回答?

会话回放系统将这些信息完整记录,让你像"看录像"一样重现整个过程。

4.2 回放数据记录

/**
* 会话回放记录器
*
* 记录粒度:每一轮对话的完整上下文快照
*
* 注意:回放数据量较大(每次对话约 10~50KB),
* 建议设置保留策略(如保留最近 30 天)
*/

@Component
public class SessionReplayRecorder {

private final ReplayRepository replayRepo;

/**
* 记录一轮完整的 ReAct 循环
*/

public void recordRound(String sessionId, int roundIndex,
ReplayRoundData data) {
ReplayRecord record = ReplayRecord.builder()
.sessionId(sessionId)
.roundIndex(roundIndex)
.timestamp(Instant.now())

// 用户输入
.userInput(data.userInput())

// 注入的上下文(System Prompt + 记忆 + RAG 结果)
.systemPrompt(data.systemPrompt())
.injectedMemories(data.injectedMemories())
.ragResults(data.ragResults())

// ReAct 循环每一步
.steps(data.steps().stream()
.map(step -> ReplayStep.builder()
.stepType(step.type()) // THOUGHT / TOOL_CALL / FINAL
.thought(step.thought())
.toolName(step.toolName())
.toolArgs(step.toolArgs())
.toolResult(step.toolResult())
.tokenUsage(step.tokenUsage())
.durationMs(step.durationMs())
.build())
.toList())

// 最终输出
.finalResponse(data.finalResponse())

// 汇总指标
.totalSteps(data.steps().size())
.totalTokens(data.totalTokens())
.totalDurationMs(data.totalDurationMs())

.build();

replayRepo.save(record);
}
}

4.3 回放 API

@RestController
@RequestMapping("/api/admin/replay")
public class SessionReplayController {

@Autowired
private ReplayRepository replayRepo;

/**
* 获取指定会话的完整回放数据
*/

@GetMapping("/sessions/{sessionId}")
public ResponseEntity<List<ReplayRecord>> getSessionReplay(
@PathVariable String sessionId) {
return ResponseEntity.ok(
replayRepo.findBySessionId(sessionId));
}

/**
* 搜索有问题的会话(按条件过滤)
*/

@GetMapping("/sessions/search")
public ResponseEntity<List<ReplaySummary>> searchSessions(
@RequestParam(required = false) String userId,
@RequestParam(required = false) String outcome,
@RequestParam(required = false) Integer minDurationMs,
@RequestParam(required = false) Integer minTokens,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {

return ResponseEntity.ok(
replayRepo.search(userId, outcome,
minDurationMs, minTokens, page, size));
}
}


五、告警体系

5.1 告警规则定义

/**
* Agent 告警规则引擎
*
* 告警分类:
* – 成本告警:费用超标
* – 性能告警:延迟异常
* – 质量告警:错误率飙升
* – 工具告警:工具调用失败率升高
*/

@Service
public class AgentAlertService {

private final AlertRuleRepository ruleRepo;
private final NotificationChannel notificationChannel;

/**
* 定时检查告警规则(每 5 分钟执行一次)
*/

@Scheduled(fixedRate = 300_000)
public void checkAlerts() {
List<AlertRule> rules = ruleRepo.findAllEnabled();

for (AlertRule rule : rules) {
double currentValue = evaluateMetric(
rule.getMetric(), rule.getWindowMinutes());

if (currentValue > rule.getThreshold()) {
AgentAlert alert = AgentAlert.builder()
.ruleName(rule.getName())
.metric(rule.getMetric())
.currentValue(currentValue)
.threshold(rule.getThreshold())
.level(rule.getLevel())
.timestamp(Instant.now())
.build();

// 告警去重:同一规则在冷却期内不重复发送
if (!isInCooldown(rule)) {
notificationChannel.send(alert);
ruleRepo.updateLastAlertTime(rule.getId());
}
}
}
}

private double evaluateMetric(String metric, int windowMinutes) {
Instant since = Instant.now()
.minus(Duration.ofMinutes(windowMinutes));

return switch (metric) {
// 成本指标
case "cost.per_query.avg" ->
traceRepo.avgCostSince(since);
case "cost.hourly.total" ->
traceRepo.totalCostSince(since);

// 性能指标
case "latency.p95" ->
traceRepo.percentileLatencySince(since, 95);
case "latency.p99" ->
traceRepo.percentileLatencySince(since, 99);

// 质量指标
case "error_rate" ->
traceRepo.errorRateSince(since);
case "tool.failure_rate" ->
traceRepo.toolFailureRateSince(since);

// 工具指标
case "tool.call_count" ->
traceRepo.toolCallCountSince(since);

default -> 0;
};
}
}

5.2 推荐的告警规则配置

# alert-rules.yml
alerts:
# ===== 成本告警 =====
name: "单次查询成本过高"
metric: cost.per_query.avg
threshold: 0.05 # 平均每次对话成本超过 $0.05
window: 60 # 过去 60 分钟
level: WARNING
cooldown: 30 # 30 分钟冷却

name: "小时成本突增"
metric: cost.hourly.total
threshold: 5.0 # 单小时总成本超过 $5
window: 60
level: CRITICAL
cooldown: 60

# ===== 性能告警 =====
name: "P95 延迟超标"
metric: latency.p95
threshold: 15000 # 95 分位延迟超过 15 秒
window: 15
level: WARNING
cooldown: 15

# ===== 质量告警 =====
name: "工具调用失败率升高"
metric: tool.failure_rate
threshold: 0.10 # 失败率超过 10%
window: 30
level: CRITICAL
cooldown: 30

name: "总体错误率升高"
metric: error_rate
threshold: 0.05 # 错误率超过 5%
window: 60
level: WARNING
cooldown: 60


六、成本优化实战

6.1 工具返回值压缩

一个常见的"Token 黑洞"是工具返回了过量的数据。比如查询能耗趋势,工具返回了 1000 行的原始数据——大模型根本不需要这么多细节。

/**
* 工具返回值压缩策略
*
* 原理:工具返回的数据量直接影响后续 LLM 调用的 Prompt Token 数。
* 压缩不必要的细节,可以显著降低成本。
*
* 策略:
* 1. 结构化摘要:将大段数据压缩为关键指标
* 2. 分页截断:超过阈值的数据只保留摘要 + 总量提示
* 3. 数值精度控制:小数位数统一为 1~2 位
*/

@Component
public class ToolResultCompressor {

/**
* 压缩能耗趋势查询结果
*
* 压缩前(约 800 tokens):
* {
* "dailyData": [
* {"date": "2026-08-21", "consumption": 8734.567, "peak": 1234.891, …},
* {"date": "2026-08-22", "consumption": 8621.234, "peak": 1198.456, …},
* … (30天的逐日数据)
* ]
* }
*
* 压缩后(约 120 tokens):
* {
* "period": "2026-08-01 ~ 2026-08-27",
* "average": 8500.0,
* "peak": 9200.0,
* "trend": "上升",
* "changeRate": "+12.5%",
* "anomalyDays": ["08-15", "08-22"]
* }
*/

public String compressTrendResult(TrendAnalysis raw) {
return String.format("""
{
"period": "%s ~ %s",
"average": %.1f,
"peak": %.1f,
"min": %.1f,
"trend": "%s",
"changeRate": "%s",
"anomalyDays": %s,
"dataPoints": %d
}
"""
,
raw.getStartDate(), raw.getEndDate(),
raw.getAverage(), raw.getPeak(), raw.getMin(),
raw.getTrend(), raw.getChangeRate(),
raw.getAnomalyDays(),
raw.getDailyData().size()
);
}
}

6.2 模型路由:按任务复杂度选模型

不是每个步骤都需要最贵的模型。一个简单的意图分类用 GPT-4o 是浪费,GPT-4o-mini 就足够了。

/**
* 智能模型路由器
*
* 根据任务复杂度自动选择合适的模型:
* – 简单任务(意图分类、摘要、提取)→ 小模型(低成本)
* – 中等任务(单步工具调用、数据查询)→ 中模型
* – 复杂任务(多步推理、复合分析)→ 大模型
*
* 实测效果:总成本降低 40%~60%,质量损失 < 3%
*/

@Component
public class ModelRouter {

private final ChatLanguageModel smallModel; // gpt-4o-mini
private final ChatLanguageModel mediumModel; // gpt-4o
private final ChatLanguageModel largeModel; // gpt-4o (高 token 配置)

/**
* 根据任务类型选择模型
*/

public ChatLanguageModel route(TaskType taskType) {
return switch (taskType) {
// 简单任务:用小模型
case INTENT_CLASSIFICATION,
CONVERSATION_SUMMARY,
USER_PROFILE_EXTRACTION,
ANSWER_JUDGMENT ->
smallModel;

// 中等任务:用中模型
case SINGLE_TOOL_CALL,
DATA_QUERY,
ALERT_ROUTING ->
mediumModel;

// 复杂任务:用大模型
case MULTI_STEP_REASONING,
COMPOSITE_ANALYSIS,
REPORT_GENERATION ->
largeModel;
};
}

/**
* 自动判断任务复杂度
*
* 判断依据(规则引擎,无需 LLM):
* – 用户消息长度 > 200 字 → 可能复杂
* – 包含多个条件/动作关键词 → 复合任务
* – 历史对话轮次 > 10 → 上下文复杂
*/

public TaskType autoDetectComplexity(String userMessage,
int conversationTurns) {
// 复合关键词检测
boolean hasCondition = userMessage.contains("如果")
|| userMessage.contains("若")
|| userMessage.contains("当");
boolean hasMultipleActions = countActions(userMessage) > 1;

if (hasCondition && hasMultipleActions) {
return TaskType.COMPOSITE_ANALYSIS;
}
if (hasMultipleActions || conversationTurns > 10) {
return TaskType.MULTI_STEP_REASONING;
}
if (userMessage.contains("报告") || userMessage.contains("分析")) {
return TaskType.REPORT_GENERATION;
}
return TaskType.SINGLE_TOOL_CALL;
}
}

6.3 Prompt 缓存:减少重复计算

在 Agent 场景中,System Prompt 通常很长(包含角色定义、工具描述、用户画像),而且在同一会话中几乎不变。Prompt 缓存可以让这部分 Token “只计费一次”。

/**
* Prompt 缓存策略
*
* 原理(以 OpenAI 为例):
* OpenAI API 支持 Prompt Caching,当请求的前缀与之前的请求
* 完全匹配时,缓存部分的 Token 只收取 50% 的价格。
*
* 关键要求:
* – 缓存命中的前提是"前缀完全一致"(字符级别)
* – 因此必须将不变内容放前面,变化内容放后面
*
* Prompt 结构优化:
* ┌─────────────────────────────┐
* │ [固定] 角色定义 + 工具描述 │ ← 始终一致,缓存命中
* │ [半固定] 用户画像 │ ← 会话内一致
* │ [变化] RAG 检索结果 │ ← 每轮可能不同
* │ [变化] 对话历史 │ ← 每轮增长
* │ [变化] 当前用户输入 │ ← 每轮不同
* └─────────────────────────────┘
*/

public class PromptCacheOptimizer {

/**
* 构建缓存友好的 Prompt 结构
*
* 核心原则:不变内容在前,变化内容在后
*/

public List<ChatMessage> buildCacheOptimizedPrompt(
String systemBase,
String userProfile,
List<TextSegment> ragResults,
List<ChatMessage> history,
String currentInput) {

StringBuilder systemPrompt = new StringBuilder();

// 第一段:固定内容(缓存命中率最高)
systemPrompt.append(systemBase);

// 第二段:用户画像(会话内稳定)
if (userProfile != null && !userProfile.isEmpty()) {
systemPrompt.append("\\n\\n").append(userProfile);
}

// 第三段:RAG 结果(每轮可能变化)
if (ragResults != null && !ragResults.isEmpty()) {
systemPrompt.append("\\n\\n相关参考信息:\\n");
ragResults.forEach(r ->
systemPrompt.append("- ").append(r.text()).append("\\n"));
}

List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from(systemPrompt.toString()));
messages.addAll(history);
messages.add(UserMessage.from(currentInput));

return messages;
}
}


七、监控看板设计建议

┌─────────────────────────────────────────────────────────────┐
│ Agent 运营监控看板 │
│ │
│ ┌───────────────────┐ ┌───────────────────┐ │
│ │ 今日对话数: 1,234 │ │ 今日成本: $23.45 │ │
│ │ (较昨日 +12%) │ │ (较昨日 +8%) │ │
│ └───────────────────┘ └───────────────────┘ │
│ │
│ ┌───────────────────┐ ┌───────────────────┐ │
│ │ P95延迟: 8.2s │ │ 错误率: 2.1% │ │
│ │ (正常) │ │ (正常) │ │
│ └───────────────────┘ └───────────────────┘ │
│ │
│ ┌─── 成本趋势(7天)──────────────────────────────────┐ │
│ │ ╱╲ │ │
│ │ ╱╲ ╱ ╲ ╱╲ │ │
│ │ ╱ ╲╱ ╲╱ ╲── │ │
│ │ Mon Tue Wed Thu Fri Sat Sun │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌─── 工具调用 Top 5 ──────────┐ ┌─── 成本分布 ──────┐ │
│ │ 1. query_energy_trend: 456 │ │ LLM: 82% │ │
│ │ 2. query_realtime: 312 │ │ RAG: 12% │ │
│ │ 3. send_alert: 89 │ │ 记忆检索: 6% │ │
│ │ 4. generate_report: 45 │ │ │ │
│ │ 5. create_ticket: 23 │ │ │ │
│ └────────────────────────────┘ └───────────────────┘ │
│ │
│ ┌─── 最近告警 ─────────────────────────────────────────┐ │
│ │ [WARNING] 10:32 单次查询成本 $0.12 > $0.05 │ │
│ │ [INFO] 09:15 P95延迟 12.3s 接近阈值 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

赞(0)
未经允许不得转载:网硕互联帮助中心 » 五:Agent 可观测性与成本优化——从“能跑“到“好管“
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!