ClaudeApiClient.java
package com.hwhub.batch.infrastructure.ai;
import com.hwhub.batch.domain.ai.AiClient;
import com.hwhub.batch.domain.enums.LocaleType;
import com.hwhub.batch.domain.model.inquiry.InquiryMessageModel;
import com.hwhub.batch.domain.model.inquiry.InquiryModel;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
@Component
@RequiredArgsConstructor
@Slf4j
public class ClaudeApiClient implements AiClient {
@Value("${hwhub.ai.claude.api-key}")
private String apiKey;
@Value("${hwhub.ai.claude.model}")
private String model;
@Value("${hwhub.ai.claude.max-tokens}")
private int maxTokens;
@Value("${hwhub.ai.claude.system-prompt}")
private String systemPrompt;
private static final Map<LocaleType, String> LOCALE_INSTRUCTION =
Map.of(
LocaleType.JA, "必ず日本語で回答してください。",
LocaleType.EN, "Please respond in English.",
LocaleType.ES, "Por favor responda en español.");
private final RestClient restClient;
@Override
public String ask(InquiryModel inquiry, String knowledge) {
try {
String localeInstruction =
LOCALE_INSTRUCTION.getOrDefault(
inquiry.getUserLocale(), LOCALE_INSTRUCTION.get(LocaleType.JA));
Map<String, Object> body =
Map.of(
"model",
model,
"max_tokens",
maxTokens,
"system",
systemPrompt + "\n\n## ナレッジベース\n" + knowledge + "\n\n## 言語指示\n" + localeInstruction,
"messages",
List.of(Map.of("role", "user", "content", buildUserMessage(inquiry))));
Map<String, Object> response =
restClient
.post()
.uri("https://api.anthropic.com/v1/messages")
.header("x-api-key", apiKey)
.header("anthropic-version", "2023-06-01")
.contentType(MediaType.APPLICATION_JSON)
.body(body)
.retrieve()
.body(new ParameterizedTypeReference<>() {});
@SuppressWarnings("unchecked")
List<Map<String, Object>> content = (List<Map<String, Object>>) response.get("content");
return (String) content.get(0).get("text");
} catch (Exception e) {
throw new RuntimeException("Claude API 呼び出し失敗 inquiryId=" + inquiry.getInquiryId(), e);
}
}
private String buildUserMessage(InquiryModel inquiry) {
StringBuilder sb = new StringBuilder();
sb.append("件名: ").append(inquiry.getTitle()).append("\n\n");
sb.append("## メッセージ履歴\n");
for (InquiryMessageModel msg : inquiry.getMessages()) {
sb.append("[").append(msg.getSenderType()).append("] ");
sb.append(msg.getBody()).append("\n");
}
return sb.toString();
}
}