Giới thiệu
Chào mừng bạn đến với phần 4 trong chuỗi bài viết "Từ Zero đến AI Agent: Hành Trình của Tôi vào Ứng Dụng Thông Minh Dựa trên Java". Trong phần này, chúng ta sẽ xây dựng một HTTP client đơn giản để kết nối với các mô hình ngôn ngữ lớn (LLMs). Điều này sẽ giúp chúng ta hiểu các truy vấn của người dùng và quyết định công cụ nào cần sử dụng.
Tại sao chọn các nhà cung cấp LLM này?
Chúng ta sẽ sử dụng hai nhà cung cấp LLM phổ biến là Groq và Google Gemini. Cả hai đều cung cấp các gói miễn phí hào phóng, lý tưởng cho việc prototyping và học hỏi:
- Groq: Cung cấp khả năng suy luận nhanh chóng với mô hình Llama, phù hợp cho các ứng dụng thời gian thực. Gói miễn phí hỗ trợ lên đến 131.072 token mỗi phút, cho phép hàng triệu token mỗi ngày để thử nghiệm nhanh chóng.
- Google Gemini: Cung cấp khả năng suy luận mạnh mẽ cho các truy vấn phức tạp. Gói miễn phí hỗ trợ lên đến 1 triệu token mỗi cửa sổ ngữ cảnh, lý tưởng cho các tác vụ nặng như tạo mã.
Cả hai đều có API REST đơn giản, thuận lợi cho việc học tập và phát triển các tích hợp AI.
Giao diện Client LLM
Chúng ta bắt đầu với một giao diện đơn giản mà bất kỳ nhà cung cấp LLM nào cũng có thể triển khai:
java
public interface LLMClient {
String send(String prompt) throws Exception;
String getProviderName();
boolean isHealthy();
}
Xây Dựng Nền Tảng HTTP
Dưới đây là lớp cơ sở với tất cả các chức năng HTTP chung:
java
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import com.fasterxml.jackson.databind.ObjectMapper;
public abstract class BaseLLMClient implements LLMClient {
protected final HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
protected final ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
protected final String apiKey;
public BaseLLMClient(String apiKey) {
this.apiKey = apiKey;
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalArgumentException("API key is required");
}
}
@Override
public String send(String prompt) throws RuntimeException {
if (prompt == null || prompt.isBlank()) {
throw new RuntimeException("Prompt không được để trống");
}
try {
HttpRequest request = buildRequest(prompt);
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Lỗi API: status=%d, body=%s".formatted(
response.statusCode(), response.body()));
}
return extractAnswer(response.body()).trim();
} catch (Exception e) {
throw new RuntimeException("Lỗi khi gửi yêu cầu: " + e.getMessage(), e);
}
}
protected abstract HttpRequest buildRequest(String prompt) throws Exception;
protected abstract String extractAnswer(String jsonResponse) throws Exception;
@Override
public boolean isHealthy() {
try {
send("Hello");
return true;
} catch (Exception e) {
return false;
}
}
}
Lớp BaseLLMClient trong Java là một lớp trừu tượng để tương tác với các API của mô hình ngôn ngữ lớn (LLM). Lớp này yêu cầu một khóa API, được xác thực trong hàm khởi tạo, và cung cấp phương thức send để truyền một prompt đến LLM, xử lý các yêu cầu và phản hồi HTTP. Phương thức buildRequest, cũng là trừu tượng, phải được triển khai bởi các lớp con để xác định cấu trúc yêu cầu HTTP.
Triển Khai Groq với Records
Groq sử dụng một API tương thích với OpenAI. Dưới đây là triển khai sạch sẽ của chúng ta sử dụng Java records:
java
import java.net.URI;
import java.net.http.HttpRequest;
import java.util.List;
public class GroqClient extends BaseLLMClient {
private static final String GROQ_URL = "https://api.groq.com/openai/v1/chat/completions";
private final String model;
public GroqClient(String apiKey, String model) {
super(apiKey);
this.model = model != null ? model : "llama-3.3-70b-versatile";
}
@Override
protected HttpRequest buildRequest(String prompt) throws Exception {
String jsonBody = objectMapper.writeValueAsString(
new GroqRequest(
model,
List.of(new GroqRequest.Message("user", prompt)),
1000,
0.1
)
);
return HttpRequest.newBuilder(URI.create(GROQ_URL))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer %s".formatted(apiKey))
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
}
@Override
protected String extractAnswer(String jsonResponse) throws Exception {
GroqResponse response = objectMapper.readValue(jsonResponse, GroqResponse.class);
List<GroqResponse.Choice> choices = response.choices();
if (choices == null || choices.isEmpty()) {
throw new IllegalStateException("Không có lựa chọn trong phản hồi");
}
return choices.get(0).message().content();
}
@Override
public String getProviderName() {
return "Groq (%s)".formatted(model);
}
}
// Định nghĩa record sạch để ánh xạ JSON
record GroqRequest(
String model,
List<Message> messages,
int max_tokens,
double temperature
) {
record Message(String role, String content) {}
}
record GroqResponse(List<Choice> choices) {
record Choice(Message message) {}
}
Triển Khai Google Gemini
Google Gemini có cấu trúc API khác, nhưng mẫu vẫn giống nhau:
java
import java.net.URI;
import java.net.http.HttpRequest;
import java.util.List;
public class GeminiClient extends BaseLLMClient {
private static final String BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models/";
private final String model;
private final String endpointUrl;
public GeminiClient(String apiKey, String model) {
super(apiKey);
this.model = model != null ? model : "gemini-1.5-flash";
this.endpointUrl = "%s%s:generateContent?key=%s".formatted(BASE_URL, this.model, apiKey);
}
@Override
protected HttpRequest buildRequest(String prompt) throws Exception {
String jsonBody = objectMapper.writeValueAsString(
new GeminiRequest(
List.of(new GeminiRequest.Content(List.of(new GeminiRequest.Part(prompt)))),
new GeminiRequest.GenerationConfig(0.1, 1000)
)
);
return HttpRequest.newBuilder(URI.create(endpointUrl))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
}
@Override
protected String extractAnswer(String jsonResponse) throws Exception {
GeminiResponse response = objectMapper.readValue(jsonResponse, GeminiResponse.class);
List<GeminiResponse.Candidate> candidates = response.candidates();
if (candidates == null || candidates.isEmpty()) {
throw new IllegalStateException("Không có ứng viên trong phản hồi");
}
List<GeminiResponse.Part> parts = candidates.get(0).content().parts();
if (parts == null || parts.isEmpty()) {
throw new IllegalStateException("Không có phần nào trong nội dung");
}
return parts.get(0).text();
}
@Override
public String getProviderName() {
return "Google Gemini (%s)".formatted(model);
}
}
// Các record yêu cầu/phản hồi của Gemini
record GeminiRequest(
List<Content> contents,
GenerationConfig generationConfig
) {
record Content(List<Part> parts) {}
record Part(String text) {}
record GenerationConfig(double temperature, int maxOutputTokens) {}
}
record GeminiResponse(List<Candidate> candidates) {
record Candidate(Content content) {}
record Content(String role, List<Part> parts) {}
record Part(String text) {}
}
Nhà Máy Đơn Giản cho Việc Tạo Dễ Dàng
Giữ cho việc tạo client sạch sẽ và đơn giản:
java
public class LLMClientFactory {
public static LLMClient createGroqClient(String apiKey) {
return new GroqClient(apiKey, "llama-3.3-70b-versatile");
}
public static LLMClient createGeminiClient(String apiKey) {
return new GeminiClient(apiKey, "gemini-1.5-flash");
}
}
Ví Dụ Sử Dụng
Dưới đây là cách đơn giản để sử dụng các client LLM của chúng ta:
java
public class LLMClientDemo {
public static void main(String[] args) {
testGroq();
testGemini();
}
private static void testGroq() {
try {
LLMClient groq = LLMClientFactory.createGroqClient(System.getenv("GROQ_API_KEY"));
System.out.println("Đang thử nghiệm " + groq.getProviderName());
String response = groq.send("2+2 bằng bao nhiêu? Trả lời ngắn gọn.");
System.out.println("Phản hồi: " + response);
System.out.println("Khỏe mạnh: " + groq.isHealthy());
System.out.println();
} catch (Exception e) {
System.out.println("Kiểm tra Groq không thành công: " + e.getMessage());
}
}
private static void testGemini() {
try {
LLMClient gemini = LLMClientFactory.createGeminiClient(System.getenv("GEMINI_API_KEY"));
System.out.println("Đang thử nghiệm " + gemini.getProviderName());
String response = gemini.send("Thủ đô của Pháp là gì? Trả lời ngắn gọn.");
System.out.println("Phản hồi: " + response);
System.out.println("Khỏe mạnh: " + gemini.isHealthy());
System.out.println();
} catch (Exception e) {
System.out.println("Kiểm tra Gemini không thành công: " + e.getMessage());
}
}
}
Kết quả mẫu:
Đang thử nghiệm Groq (llama-3.3-70b-versatile)
Phản hồi: 2+2 = 4
Khỏe mạnh: true
Đang thử nghiệm Google Gemini (gemini-1.5-flash)
Phản hồi: Thủ đô của Pháp là Paris.
Khỏe mạnh: true
Thiết Lập Môi Trường
Hãy chắc chắn thiết lập các khóa API của bạn dưới dạng biến môi trường:
bash
# Windows
set GROQ_API_KEY=your_groq_key_here
set GEMINI_API_KEY=your_gemini_key_here
# Unix/MacOS
export GROQ_API_KEY=your_groq_key_here
export GEMINI_API_KEY=your_gemini_key_here
Những Điều Tiếp Theo
Trong bài viết tiếp theo, chúng ta sẽ xây dựng một chiến lược suy luận đơn giản để kết nối giữa các truy vấn của người dùng và thực thi công cụ. Chúng ta sẽ giữ cho nó tối giản và tập trung - đủ để hiểu các khái niệm cốt lõi.
Nền tảng HTTP sạch sẽ mà chúng ta đã xây dựng hôm nay sẽ hỗ trợ tất cả các tính năng thông minh trong client MCP Java của chúng ta!
Đây là phần 4 trong chuỗi bài viết "Từ Zero đến AI Agent: Hành Trình của Tôi vào Ứng Dụng Thông Minh Dựa trên Java". Tiếp theo: các chiến lược suy luận kết nối mọi thứ lại với nhau!