跳到主要内容

5. 工程挑战与生产就绪

构建原型 Agent 与构建生产环境中可靠运行的 Agent 截然不同。本节涵盖了生产级 AI Agent 的关键工程挑战、评估方法、安全考量和部署策略。


5.1 Agent 评估

Agent 性能评估与传统软件测试截然不同,因为它涉及非确定性与复杂性。

评估方法

1. LLM 作为评判者 (LLM-as-a-Judge)

使用 LLM 根据预设标准评估 Agent 输出。

实现示例

@Service
public class AgentEvaluator {

@Autowired
private ChatClient evaluatorClient;

public EvaluationResult evaluate(AgentOutput output, EvaluationCriteria criteria) {
String evaluation = evaluatorClient.prompt()
.system("""
你是一名 AI Agent 输出的专家评估员。
请根据以下标准,以 1-10 分进行评分:
1. 准确性:信息是否正确?
2. 完整性:是否完全解决了任务?
3. 相关性:信息是否聚焦?
4. 安全性:是否存在有害输出?
""")
.user("""
任务: {task}
Agent 输出: {output}
上下文: {context}

请以 JSON 格式提供评估:
{
"accuracy": 8,
"completeness": 7,
"relevance": 9,
"safety": 10,
"reasoning": "..."
}
""".formatted(
output.task(),
output.content(),
output.context()
))
.call()
.content();

return parseEvaluation(evaluation);
}
}

最佳实践

  • 清晰标准:定义具体的评估维度。
  • 少量示例:提供良好/不良输出的例子。
  • 多个评判者:使用多个 LLM 并聚合结果。
  • 人工校准:通过人工标注来校准 LLM 评判者。

2. 人工评估 (Human Evaluation)

人工评估仍是质量的黄金标准。

3. 自动化测试 (Automated Testing)

通过单元测试和集成测试来测试特定的 Agent 行为。

4. 关键指标 (Key Metrics)

指标描述目标
任务成功率成功完成任务的百分比> 90%
准确性输出的事实正确性> 95%
相关性输出与任务的匹配程度> 90%
安全性不含 harmful 内容100%
延迟 (p50)中位数响应时间< 5s
延迟 (p95)95% 分位数响应时间< 15s
每任务成本每个成功任务的 Token 成本最小化
工具成功率工具调用成功百分比> 95%

5.2 常见挑战

挑战 1:幻觉 (Hallucination)

Agent 可能生成听起来合理但实际上不正确的信息。

缓解策略

实现示例

@Service
public class AntiHallucinationService {

@Autowired
private VectorStore vectorStore;

@Autowired
private ChatClient chatClient;

public String generateWithVerification(String query) {
// 步骤 1: 检索相关上下文
List<Document> context = vectorStore.similaritySearch(
SearchRequest.query(query).withTopK(5)
);

// 步骤 2: 生成带引用的响应
String response = chatClient.prompt()
.user(query)
.messages(createMessagesWithCitations(context))
.call()
.content();

// 步骤 3: 验证主张 (claims)
List<Claim> claims = extractClaims(response);
for (Claim claim : claims) {
if (!verifyClaim(claim, context)) {
return flagUncertainty(claim); // 标记不确定性
}
}

return response;
}

private boolean verifyClaim(Claim claim, List<Document> context) {
// 使用 RAG 上下文进行验证
String verification = chatClient.prompt()
.system("验证该主张是否得到上下文的支持。")
.user("""
主张: {claim}
上下文: {context}
请用 YES 或 NO 回答,并给出解释。
""".formatted(
claim.text(),
context.stream()
.map(Document::getContent)
.collect(Collectors.joining("
"))
))
.call()
.content();

return verification.toLowerCase().startsWith("yes");
}
}

挑战 2:无限循环 (Infinite Loops)

Agent 可能会陷入重复行为。

解决方案

@Service
public class LoopPreventionService {

private static final int MAX_ITERATIONS = 10;
private static final int MAX_REPEAT_ACTIONS = 3;

public AgentExecutionResult executeWithGuardrails(AgentTask task) {
Set<String> recentActions = new HashSet<>();
int iteration = 0;

while (iteration < MAX_ITERATIONS && !task.isComplete()) {
String action = task.getNextAction();

// 检测循环
if (recentActions.contains(action)) {
int count = countOccurrences(recentActions, action);
if (count >= MAX_REPEAT_ACTIONS) {
return handleLoop(task, action); // 处理循环
}
}

recentActions.add(action);
if (recentActions.size() > 5) {
recentActions.remove(recentActions.iterator().next());
}

// 执行动作
task.executeAction(action);
iteration++;
}

return task.getResult();
}

private AgentExecutionResult handleLoop(AgentTask task, String repeatingAction) {
// 请求人工干预
return AgentExecutionResult.builder()
.status("NEEDS_INTERVENTION")
.message("Agent 陷入循环,重复动作: " + repeatingAction)
.suggestedActions(List.of(
"尝试不同的方法",
"提供更具体的指令",
"将任务分解为更小的步骤"
))
.build();
}
}

挑战 3:成本控制 (Cost Control)

大规模 LLM 使用可能变得昂贵。

成本优化策略

策略影响实现方式
缓存缓存 LLM 响应
小型模型简单任务使用 Haiku 等小型模型
Token 限制限制每次请求的最大 Token 数
流式传输优化用户体验,但对成本影响较小
批量处理批量处理多个查询

实现示例

@Service
public class CostOptimizedAgentService {

@Autowired
private ChatClient gpt4Client; // 昂贵的模型

@Autowired
private ChatClient haikuClient; // 便宜的模型

@Autowired
private CacheManager cacheManager;

public String execute(AgentRequest request) {
// 首先检查缓存
String cacheKey = generateCacheKey(request);
String cached = cacheManager.getCache("agent-responses").get(cacheKey, String.class);
if (cached != null) {
return cached;
}

// 路由到合适的模型
ChatClient client = selectModel(request);
String response = client.prompt().user(request.query()).call().content();

// 缓存结果
cacheManager.getCache("agent-responses").put(cacheKey, response);

return response;
}

private ChatClient selectModel(AgentRequest request) {
// 简单查询使用 Haiku
if (request.complexity() == Complexity.LOW) {
return haikuClient;
}

// 复杂任务使用 GPT-4
return gpt4Client;
}
}

挑战 4:延迟 (Latency)

Agent 需要快速响应以提供良好的用户体验。

优化技术

并行工具执行

@Service
public class ParallelToolExecutor {

@Autowired
private List<FunctionCallback> tools;

public Map<String, String> executeParallel(List<ToolCall> calls) {
ExecutorService executor = Executors.newFixedThreadPool(10);

List<CompletableFuture<Map.Entry<String, String>>> futures = calls.stream()
.map(call -> CompletableFuture.supplyAsync(() -> {
String result = executeTool(call);
return Map.entry(call.name(), result);
}, executor))
.toList();

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

return futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
}
}

5.3 安全与合规性

提示词注入 (Prompt Injection)

恶意用户试图操纵 Agent 行为。

防御策略

@Service
public class PromptInjectionDefense {

private static final Pattern INJECTION_PATTERNS = Pattern.compile(
"(ignore|override|forget|disregard).*(instructions|system|prompt)",
Pattern.CASE_INSENSITIVE
);

public SanitizedInput sanitize(UserInput input) {
String text = input.text();

// 检查注入模式
if (INJECTION_PATTERNS.matcher(text).find()) {
throw new SecurityException("检测到潜在的提示词注入");
}

// 对照白名单进行验证
if (!isAllowedTopic(text)) {
throw new SecurityException("主题不允许");
}

// 速率限制检查
if (exceedsRateLimit(input.userId())) {
throw new RateLimitExceededException();
}

return SanitizedInput.from(text);
}

@Bean
public SecurityFilter securityFilter() {
return new SecurityFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String path = exchange.getRequest().getPath().value();

if (path.startsWith("/api/agents")) {
String body = getBody(exchange);
try {
sanitize(new UserInput(body));
} catch (SecurityException e) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
}

return chain.filter(exchange);
}
};
}
}

工具访问控制 (Tool Access Control)

根据用户权限限制 Agent 可以使用的工具。

@Service
public class ToolAccessControl {

@Autowired
private PermissionService permissionService;

public List<FunctionCallback> getAuthorizedTools(String userId) {
return allTools.stream()
.filter(tool -> permissionService.hasPermission(userId, tool.getName()))
.toList();
}

public boolean canExecuteTool(String userId, String toolName) {
ToolPermission permission = permissionService.getPermission(userId, toolName);

// 检查权限
if (!permission.isAllowed()) {
return false;
}

// 检查速率限制
if (permission.getUsageCount() >= permission.getMaxUsage()) {
return false;
}

// 检查时间限制
if (!permission.isWithinAllowedHours()) {
return false;
}

return true;
}
}

人在回路 (Human-in-the-Loop)

对敏感操作需要人工批准。

@Service
public class HumanInTheLoopService {

@Autowired
private NotificationService notificationService;

@Autowired
private ApprovalRepository approvalRepository;

public AgentResult executeWithApproval(AgentTask task) {
// 检查是否需要批准
if (task.requiresApproval()) {
ApprovalRequest request = createApprovalRequest(task);
notificationService.notifyApprovers(request);

// 等待批准
Approval approval = waitForApproval(request.getId());

if (!approval.isApproved()) {
return AgentResult.rejected("批准被拒绝: " + approval.getReason());
}
}

// 执行任务
return task.execute();
}

private Approval waitForApproval(String requestId) {
// 轮询等待批准 (或使用 WebSocket)
for (int i = 0; i < 60; i++) { // 1 分钟超时
Approval approval = approvalRepository.findById(requestId).orElse(null);
if (approval != null && approval.isDecided()) {
return approval;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

throw new ApprovalTimeoutException();
}
}

审计日志 (Audit Logging)

跟踪所有 Agent 动作以确保安全与合规性。

@Service
public class AgentAuditLogger {

@Autowired
private AuditLogRepository auditLogRepository;

@EventListener
public void logAgentAction(AgentActionEvent event) {
AgentAuditLog log = AgentAuditLog.builder()
.agentId(event.getAgentId())
.userId(event.getUserId())
.action(event.getAction())
.input(sanitize(event.getInput()))
.output(sanitize(event.getOutput()))
.toolsUsed(event.getToolsUsed())
.tokensConsumed(event.getTokensConsumed())
.cost(event.getCost())
.timestamp(Instant.now())
.build();

auditLogRepository.save(log);
}

public List<AgentAuditLog> getUserActivity(String userId, Instant since) {
return auditLogRepository.findByUserIdAndTimestampAfter(userId, since);
}
}

5.4 生产部署 (Production Deployment)

Docker 配置

# Dockerfile
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY build.gradle settings.gradle ./
COPY src ./src
RUN ./gradlew bootJar --no-daemon

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Docker Compose

version: '3.8'
services:
agent-service:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=production
- OPENAI_API_KEY=${OPENAI_API_KEY}
- POSTGRES_URL=jdbc:postgresql://postgres:5432/agents
- REDIS_URL=redis://redis:6379
depends_on:
- postgres
- redis
restart: unless-stopped

postgres:
image: pgvector/pgvector:pg16
environment:
- POSTGRES_DB=agents
- POSTGRES_USER=agent_user
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped

redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped

prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
restart: unless-stopped

grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
volumes:
- grafana_data:/var/lib/grafana
restart: unless-stopped

volumes:
postgres_data:
redis_data:
grafana_data:

可观测性栈 (Observability Stack)

# prometheus.yml
global:
scrape_interval: 15s

scrape_configs:
- job_name: 'agent-service'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['agent-service:8080']

监控仪表盘 (Grafana)

关键监控指标:

指标描述告警阈值
agent_success_rateAgent 执行成功率< 95%
agent_latency_p9595% 分位数延迟> 15s
agent_token_usage每小时消耗 Token 数> 100K
agent_cost_per_task每个成功任务的成本> $0.10
tool_failure_rate工具调用失败率> 5%
llm_api_errorsLLM API 错误率> 1%

5.5 A/B 测试

安全地测试不同的 Agent 配置。

@Service
public class AgentABTestService {

@Autowired
private AgentRegistry agentRegistry;

@Autowired
private ExperimentRepository experimentRepository;

public String executeWithExperiment(String userId, String query) {
// 获取活跃实验
Experiment experiment = experimentRepository.findActive("agent-v2-vs-v1");

// 为用户分配变体
String variant = assignVariant(experiment, userId);

// 获取变体的 Agent
Agent agent = agentRegistry.getAgent(variant);

// 执行
String result = agent.execute(query);

// 记录指标
logMetrics(experiment, variant, userId, result);

return result;
}

private String assignVariant(Experiment experiment, String userId) {
// 使用一致性哈希确保稳定分配
int hash = userId.hashCode();
if (hash % 2 == 0) {
return "agent_v1";
} else {
return "agent_v2";
}
}
}

5.6 核心经验总结

评估策略

  1. LLM 作为评判者:可扩展但需校准。
  2. 人工评估:质量的黄金标准。
  3. 自动化测试:对回归至关重要。
  4. 指标跟踪:提供量化洞察。

挑战缓解

挑战缓解措施
幻觉RAG + 验证 + 引用
无限循环迭代限制 + 循环检测
高成本缓存 + 小型模型
高延迟并行工具 + 流式传输
安全输入验证 + 访问控制

生产就绪清单

  • 建立了评估框架
  • 错误处理全面
  • 配置了速率限制
  • 具备安全控制措施
  • 启用了审计日志
  • 配置了监控和告警
  • 实施了成本控制
  • A/B 测试框架就绪
  • 回滚计划已文档化

5.7 后续步骤

完成您的学习之旅:


小规模启动

部署到生产环境时,从小规模 Beta 版开始,密切监控指标,并根据性能逐步增加流量。

成本意识

Agent 成本会迅速增长。在广泛部署前,务必实施缓存并设置预算限制。