使用LangChain4j构建Java AI智能体:让大模型学会使用工具

简介: AI智能体是大模型技术的重要演进方向,它使模型能够主动使用工具、与环境交互,以完成复杂任务。本文详细介绍如何在Java应用中,借助LangChain4j框架构建一个具备工具使用能力的AI智能体。我们将创建一个能够进行数学计算和实时信息查询的智能体,涵盖工具定义、智能体组装、记忆管理以及Spring Boot集成等关键步骤,并展示如何通过简单的对话界面与智能体交互。

一、 引言:从被动问答到主动工具使用
大模型在知识问答和文本生成方面表现出色,但在处理实时信息、精确计算或访问私有系统时存在局限。AI智能体通过赋予模型使用工具的能力,突破了这一限制。例如,当用户询问“今天北京的天气怎么样?”时,智能体可以自动调用天气查询工具,获取实时天气并生成回答。

在Java生态中,LangChain4j提供了强大的智能体构建能力。通过将工具(Tools)与模型(Model)结合,并引入推理逻辑,我们可以创建出能够自主规划、执行动作的AI应用。

二、 项目搭建与依赖配置

  1. 项目依赖

我们使用Spring Boot和LangChain4j来构建智能体。在pom.xml中引入以下依赖:

xml


0.29.0



org.springframework.boot
spring-boot-starter-web

<!-- LangChain4j 核心 -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
    <version>${langchain4j.version}</version>
</dependency>

<!-- LangChain4j 用于OpenAI集成 -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai</artifactId>
    <version>${langchain4j.version}</version>
</dependency>

<!-- 用于工具调用(例如:HTTP请求) -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-tool-spring</artifactId>
    <version>${langchain4j.version}</version>
</dependency>

<!-- Spring Boot Configuration Processor -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

  1. 配置信息(application.yml)

yaml
langchain4j:
openai:
chat-model:
api-key: ${OPENAI_API_KEY}
model-name: "gpt-3.5-turbo" # 或 "gpt-4"
三、 核心实现:定义工具与组装智能体

  1. 定义工具(Tools)

工具是智能体与环境交互的接口。我们定义两个工具:一个计算器和一个网络搜索工具。

java
import org.springframework.stereotype.Component;
import dev.langchain4j.agent.tool.Tool;
import org.springframework.web.client.RestTemplate;
import java.util.Map;

@Component
public class CalculatorTool {

@Tool("用于计算两个数字的加法、减法、乘法和除法")
public double calculate(double a, double b, String operator) {
    switch (operator) {
        case "+": return a + b;
        case "-": return a - b;
        case "*": return a * b;
        case "/": 
            if (b == 0) throw new IllegalArgumentException("除数不能为零");
            return a / b;
        default: throw new IllegalArgumentException("不支持的操作符: " + operator);
    }
}

}
java
@Component
public class WebSearchTool {

private final RestTemplate restTemplate = new RestTemplate();

@Tool("在互联网上搜索实时信息,例如当前新闻、天气等")
public String searchWeb(String query) {
    // 这里我们使用一个模拟的搜索API,实际应用中可以使用SerpApi、DuckDuckGo等
    // 为了演示,我们返回一个固定的字符串,实际中应该调用真实的搜索API
    return "根据搜索查询'" + query + "',这里是模拟的搜索结果:今天北京晴,气温20-25摄氏度。";
}

}

  1. 组装智能体

我们使用LangChain4j的AiServices来组装智能体,将工具和模型结合起来。

java
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 定义智能体接口
interface Assistant {
String chat(String userMessage);
}

@Configuration
public class AgentConfiguration {

@Bean
public Assistant assistant(CalculatorTool calculatorTool, WebSearchTool webSearchTool) {
    return AiServices.builder(Assistant.class)
            .chatLanguageModel(OpenAiChatModel.builder()
                    .apiKey(System.getenv("OPENAI_API_KEY"))
                    .modelName("gpt-3.5-turbo")
                    .temperature(0.1) // 降低随机性,使工具调用更稳定
                    .build())
            .tools(calculatorTool, webSearchTool)
            .build();
}

}

  1. 创建REST控制器

java
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/agent")
public class AgentController {

private final Assistant assistant;

public AgentController(Assistant assistant) {
    this.assistant = assistant;
}

@PostMapping("/chat")
public String chat(@RequestBody String userMessage) {
    return assistant.chat(userMessage);
}

}
四、 测试与进阶功能

  1. 测试智能体

启动应用后,我们可以通过curl或Postman发送请求:

bash
curl -X POST http://localhost:8080/api/agent/chat \
-H "Content-Type: text/plain" \
-d "请计算125乘以36等于多少?"
智能体会自动选择计算器工具,并返回计算结果。

bash
curl -X POST http://localhost:8080/api/agent/chat \
-H "Content-Type: text/plain" \
-d "今天北京天气怎么样?"
智能体会调用网络搜索工具,并返回模拟的天气信息。

  1. 添加记忆功能

为了让智能体记住对话上下文,我们可以添加记忆功能。LangChain4j提供了ChatMemory接口,我们可以使用TokenWindowChatMemory来限制记忆的令牌数。

修改AgentConfiguration:

java
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
import dev.langchain4j.model.openai.OpenAiTokenizer;

@Configuration
public class AgentConfiguration {

@Bean
public Assistant assistant(CalculatorTool calculatorTool, WebSearchTool webSearchTool) {
    return AiServices.builder(Assistant.class)
            .chatLanguageModel(OpenAiChatModel.builder()
                    .apiKey(System.getenv("OPENAI_API_KEY"))
                    .modelName("gpt-3.5-turbo")
                    .temperature(0.1)
                    .build())
            .tools(calculatorTool, webSearchTool)
            .chatMemory(TokenWindowChatMemory.withMaxTokens(1000, new OpenAiTokenizer("gpt-3.5-turbo")))
            .build();
}

}
现在,智能体可以记住之前的对话。例如,用户先问“今天北京天气怎么样?”,再问“那上海呢?”,智能体会知道“上海”指的是天气。

  1. 工具调用的控制

我们可以通过@Tool注解的name和description来精确控制工具的使用。模型会根据描述决定是否调用工具以及如何调用。

五、 总结
通过LangChain4j,我们成功在Java中构建了一个能够使用工具的AI智能体。这个智能体不仅能够回答一般性问题,还能通过调用工具完成数学计算和实时信息查询等任务。我们展示了如何定义工具、组装智能体、集成Spring Boot,并添加记忆功能。

这种智能体架构可以扩展到更复杂的场景,例如连接数据库、调用企业内部API、发送邮件等。通过将大模型与具体工具结合,我们能够构建出真正智能、自动化的Java应用,为企业级AI解决方案提供了强大的技术支持。

随着AI智能体技术的不断发展,Java开发者可以利用熟悉的工具和框架,轻松构建出下一代智能应用。

标题:构建生产级AI应用:Java与大模型集成的工程化实践
摘要: 将大模型集成到Java应用中不仅仅是调用API那么简单。要构建稳定、可扩展的生产级AI应用,需要系统性的工程化思考。本文深入探讨在Java生态中集成大模型时面临的关键工程挑战:性能优化、容错设计、成本控制、监控可观测性和部署策略。通过具体的代码示例和架构模式,展示如何构建一个具备弹性、可观测且高效的智能Java应用。

文章内容
一、 引言:从Demo到生产的关键跨越
在成功构建了AI集成的技术原型后,我们面临着一个更严峻的挑战:如何将其转化为能够承受生产环境压力的企业级系统。一个简单的API调用在Demo中可能工作良好,但在真实场景中需要应对:

性能瓶颈:大模型API的响应时间在几百毫秒到数秒不等

服务不稳定:第三方API可能面临限流、故障或网络波动

成本激增:不当的使用模式可能导致API费用失控

调试困难:黑盒模型的行为难以追踪和复现

本文将系统性地解决这些挑战,提供一套完整的工程化解决方案。

二、 架构设计:构建弹性的AI服务层

  1. 服务抽象与多模型支持

首先,我们应该设计一个抽象层,避免与特定模型提供商强耦合。

java
// 统一的AI服务接口
public interface AIService {
CompletionResult complete(CompletionRequest request);
Stream streamComplete(CompletionRequest request);
}

// 请求和响应的通用模型
public record CompletionRequest(
String prompt,
Double temperature,
Integer maxTokens,
String modelVariant
) {}

public record CompletionResult(
String content,
String modelUsed,
Usage usage,
Long processingTimeMs
) {}

public record Usage(int promptTokens, int completionTokens) {}

  1. 实现带熔断和重试的OpenAI客户端

使用Resilience4j实现容错模式:

java
@Component
@Slf4j
public class ResilientOpenAIService implements AIService {

private final OpenAIClient delegate;
private final CircuitBreaker circuitBreaker;
private final Retry retry;
private final RateLimiter rateLimiter;

public ResilientOpenAIService(OpenAIClient delegate) {
    this.delegate = delegate;

    // 配置熔断器:在50%的请求失败时打开,30秒后进入半开状态
    this.circuitBreaker = CircuitBreaker.ofDefaults("openai-cb");

    // 配置重试:最多3次,使用指数退避
    this.retry = Retry.of("openai-retry", RetryConfig.custom()
            .maxAttempts(3)
            .waitDuration(Duration.ofSeconds(1))
            .retryOnResult(this::shouldRetry)
            .build());

    // 配置限流:每秒最多5个请求
    this.rateLimiter = RateLimiter.of("openai-rl", RateLimiterConfig.custom()
            .limitForPeriod(5)
            .limitRefreshPeriod(Duration.ofSeconds(1))
            .build());
}

@Override
public CompletionResult complete(CompletionRequest request) {
    return CircuitBreaker.decorateFunction(circuitBreaker,
            Retry.decorateFunction(retry,
            RateLimiter.decorateFunction(rateLimiter, this::doComplete)))
            .apply(request);
}

private CompletionResult doComplete(CompletionRequest request) {
    long startTime = System.currentTimeMillis();
    try {
        // 实际调用OpenAI API
        ChatCompletionResponse response = delegate.chatCompletion(
            createOpenAIRequest(request));

        return new CompletionResult(
            extractContent(response),
            request.modelVariant(),
            new Usage(
                response.usage().promptTokens(),
                response.usage().completionTokens()
            ),
            System.currentTimeMillis() - startTime
        );
    } catch (Exception e) {
        log.error("OpenAI API调用失败", e);
        throw new AIServiceException("AI服务暂时不可用", e);
    }
}

private boolean shouldRetry(CompletionResult result) {
    // 根据业务逻辑决定是否需要重试
    // 例如:内容被过滤、token超限等可重试错误
    return false;
}

}
三、 性能优化:缓存与异步处理

  1. 智能响应缓存

对于相似的用户查询,我们可以通过语义缓存避免重复调用API。

java
@Component
public class SemanticCache {

private final Cache<Embedding, CachedCompletion> cache;
private final EmbeddingModel embeddingModel;

public SemanticCache(EmbeddingModel embeddingModel) {
    this.embeddingModel = embeddingModel;
    this.cache = Caffeine.newBuilder()
            .maximumSize(10_000)
            .expireAfterWrite(Duration.ofHours(24))
            .build();
}

public Optional<CompletionResult> get(String prompt, double similarityThreshold) {
    float[] promptEmbedding = embeddingModel.embed(prompt).content().vector();

    return cache.asMap().entrySet().stream()
            .filter(entry -> cosineSimilarity(promptEmbedding, 
                    entry.getKey().vector()) > similarityThreshold)
            .map(Entry::getValue)
            .map(CachedCompletion::result)
            .findFirst();
}

public void put(String prompt, CompletionResult result) {
    float[] embedding = embeddingModel.embed(prompt).content().vector();
    cache.put(new Embedding(embedding), new CachedCompletion(result, System.currentTimeMillis()));
}

private double cosineSimilarity(float[] a, float[] b) {
    // 计算余弦相似度的实现
    double dotProduct = 0.0;
    double normA = 0.0;
    double normB = 0.0;
    for (int i = 0; i < a.length; i++) {
        dotProduct += a[i] * b[i];
        normA += Math.pow(a[i], 2);
        normB += Math.pow(b[i], 2);
    }
    return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}

record Embedding(float[] vector) {}
record CachedCompletion(CompletionResult result, long timestamp) {}

}

  1. 异步处理与批量化

对于非实时场景,我们可以使用批处理来优化成本。

java
@Component
public class BatchAIService {

private final AIService aiService;
private final ExecutorService executor;
private final BatchProcessor<BatchRequest, BatchResult> batchProcessor;

public BatchAIService(AIService aiService) {
    this.aiService = aiService;
    this.executor = Executors.newFixedThreadPool(10);

    this.batchProcessor = BatchProcessor.<BatchRequest, BatchResult>builder()
            .bufferSize(100)
            .bufferTime(Duration.ofSeconds(5))
            .processor(this::processBatch)
            .build();
}

public CompletableFuture<CompletionResult> processAsync(String prompt) {
    return CompletableFuture.supplyAsync(() -> 
        aiService.complete(new CompletionRequest(prompt, 0.7, 1000, "gpt-3.5-turbo")), 
        executor);
}

public CompletableFuture<BatchResult> processInBatch(BatchRequest request) {
    return batchProcessor.process(request);
}

private List<BatchResult> processBatch(List<BatchRequest> requests) {
    // 批量处理逻辑,可以优化token使用
    // 例如将多个相似请求合并为一个更高效的提示
    return requests.stream()
            .map(req -> new BatchResult(req.id(), 
                aiService.complete(req.toCompletionRequest())))
            .collect(Collectors.toList());
}

}
四、 可观测性与监控

  1. 结构化日志与链路追踪

java
@Aspect
@Component
@Slf4j
public class AIServiceMonitoringAspect {

@Around("execution(* com.example.aiservice..*.*(..))")
public Object monitorAICalls(ProceedingJoinPoint joinPoint) throws Throwable {
    String methodName = joinPoint.getSignature().getName();
    String traceId = MDC.get("traceId") != null ? MDC.get("traceId") : 
                    UUID.randomUUID().toString().substring(0, 8);

    long startTime = System.currentTimeMillis();
    try {
        Object result = joinPoint.proceed();
        long duration = System.currentTimeMillis() - startTime;

        // 结构化日志,便于后续分析
        log.info("AI调用成功: method={}, traceId={}, durationMs={}", 
                methodName, traceId, duration);

        if (result instanceof CompletionResult) {
            CompletionResult completion = (CompletionResult) result;
            Metrics.counter("ai.completion.tokens", 
                "model", completion.modelUsed())
                .increment(completion.usage().completionTokens());
        }

        return result;
    } catch (Exception e) {
        long duration = System.currentTimeMillis() - startTime;
        log.error("AI调用失败: method={}, traceId={}, durationMs={}, error={}", 
                methodName, traceId, duration, e.getMessage());
        Metrics.counter("ai.errors", "type", e.getClass().getSimpleName()).increment();
        throw e;
    }
}

}

  1. 成本监控与预警

java
@Component
public class CostMonitor {

private final MeterRegistry meterRegistry;
private final AtomicDouble dailyCost = new AtomicDouble(0.0);
private final double budgetLimit;

public CostMonitor(MeterRegistry meterRegistry, 
                  @Value("${ai.monthly.budget:100}") double monthlyBudget) {
    this.meterRegistry = meterRegistry;
    this.budgetLimit = monthlyBudget / 30; // 每日预算

    // 注册成本指标
    Gauge.builder("ai.daily.cost", dailyCost, AtomicDouble::get)
            .register(meterRegistry);

    // 定时重置每日成本
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    scheduler.scheduleAtFixedRate(this::resetDailyCost, 24, 24, TimeUnit.HOURS);
}

public void recordCompletion(String model, int promptTokens, int completionTokens) {
    double cost = calculateCost(model, promptTokens, completionTokens);
    dailyCost.addAndGet(cost);

    // 预算预警
    if (dailyCost.get() > budgetLimit * 0.8) {
        log.warn("AI服务日成本接近预算限制: current={}, limit={}", 
                dailyCost.get(), budgetLimit);
    }
}

private double calculateCost(String model, int promptTokens, int completionTokens) {
    // 根据模型和token数量计算成本
    // 这里使用OpenAI的定价示例
    switch (model) {
        case "gpt-4":
            return (promptTokens * 0.03 + completionTokens * 0.06) / 1000;
        case "gpt-3.5-turbo":
            return (promptTokens + completionTokens) * 0.0015 / 1000;
        default:
            return 0.0;
    }
}

private void resetDailyCost() {
    dailyCost.set(0.0);
}

}
五、 部署与配置管理

  1. 环境特定的配置

yaml

application-production.yml

ai:
service:
timeout: 30000
max-retries: 3
circuit-breaker:
failure-rate-threshold: 50
wait-duration: 30s
cache:
enabled: true
similarity-threshold: 0.95
budget:
daily-limit: 50.0 # 生产环境每日预算

management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
endpoint:
health:
show-details: always

  1. 健康检查

java
@Component
public class AIServiceHealthIndicator implements HealthIndicator {

private final AIService aiService;

public AIServiceHealthIndicator(AIService aiService) {
    this.aiService = aiService;
}

@Override
public Health health() {
    try {
        // 简单的健康检查:发送一个低成本测试请求
        CompletionResult result = aiService.complete(
            new CompletionRequest("测试", 0.1, 5, "gpt-3.5-turbo"));

        if (result != null && result.content() != null) {
            return Health.up()
                .withDetail("model", result.modelUsed())
                .withDetail("responseTime", result.processingTimeMs() + "ms")
                .build();
        } else {
            return Health.down().withDetail("reason", "Empty response").build();
        }
    } catch (Exception e) {
        return Health.down(e).build();
    }
}

}
六、 总结
构建生产级的Java AI应用需要超越简单的API调用,从架构层面系统性地解决性能、可靠性、成本和可观测性等关键问题。通过本文介绍的工程化实践:

容错设计:使用熔断、重试和限流确保系统弹性

性能优化:通过缓存、异步和批处理提升吞吐量

成本控制:实现细粒度的成本监控和预警机制

可观测性:建立完整的监控、日志和追踪体系

配置管理:支持不同环境的灵活配置

这些模式使得Java AI应用能够真正满足企业级的需求,在享受AI能力带来的业务价值的同时,确保系统的稳定性、可控性和成本效益。随着AI技术的快速发展,这些工程化基础将成为构建下一代智能应用的坚实基石。

目录
相关文章
|
11天前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
1244 5
|
10天前
|
机器学习/深度学习 人工智能 前端开发
通义DeepResearch全面开源!同步分享可落地的高阶Agent构建方法论
通义研究团队开源发布通义 DeepResearch —— 首个在性能上可与 OpenAI DeepResearch 相媲美、并在多项权威基准测试中取得领先表现的全开源 Web Agent。
1230 87
|
11天前
|
云栖大会
阿里云云栖大会2025年9月24日开启,免费申请大会门票,速度领取~
2025云栖大会将于9月24-26日举行,官网免费预约畅享票,审核后短信通知,持证件入场
1804 13
|
20天前
|
人工智能 运维 安全
|
4天前
|
资源调度
除了nrm-pm,还有哪些工具可以管理多个包管理器的源?
除了nrm-pm,还有哪些工具可以管理多个包管理器的源?
236 127
|
4天前
|
前端开发
Promise的then方法返回的新Promise对象有什么特点?
Promise的then方法返回的新Promise对象有什么特点?
182 2