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 /app/build/libs/*.jar app.jar
# 健康检查
HEALTHCHECK
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_rate | Agent 执行成功率 | < 95% |
| agent_latency_p95 | 95% 分位数延迟 | > 15s |
| agent_token_usage | 每小时消耗 Token 数 | > 100K |
| agent_cost_per_task | 每个成功任务的成本 | > $0.10 |
| tool_failure_rate | 工具调用失败率 | > 5% |
| llm_api_errors | LLM 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 核心经验总结
评估策略
- LLM 作为评判者:可扩展但需校准。
- 人工评估:质量的黄金标准。
- 自动化测试:对回归至关重要。
- 指标跟踪:提供量化洞察。
挑战缓解
| 挑战 | 缓解措施 |
|---|---|
| 幻觉 | RAG + 验证 + 引用 |
| 无限循环 | 迭代限制 + 循环检测 |
| 高成本 | 缓存 + 小型模型 |
| 高延迟 | 并行工具 + 流式传输 |
| 安全 | 输入验证 + 访问控制 |
生产就绪清单
- 建立了评估框架
- 错误处理全面
- 配置了速率限制
- 具备安全控制措施
- 启用了审计日志
- 配置了监控和告警
- 实施了成本控制
- A/B 测试框架就绪
- 回滚计划已文档化
5.7 后续步骤
完成您的学习之旅:
- → 6. 前沿趋势 - 新兴技术与研究
小规模启动
部署到生产环境时,从小规模 Beta 版开始,密切监控指标,并根据性能逐步增加流量。
成本意识
Agent 成本会迅速增长。在广泛部署前,务必实施缓存并设置预算限制。