OpenAI 통합
OpenAI 통합
LangChain4j에서 OpenAI 모델을 쓰는 방법을 다룰게요. 이 문서에서 설명하는 OpenAI 통합은 OpenAI REST API를 자바로 직접 구현한 방식이라, Quarkus(Quarkus REST client를 씀)나 Spring(Spring의 RestClient를 씀)에서 가장 잘 동작해요.
LangChain4j는 OpenAI 채팅 모델을 위한 통합을 세 가지 제공하는데, 이 페이지가 그중 1번이에요.
- OpenAI — OpenAI REST API의 자바 사용자 구현으로, Quarkus·Spring에 잘 맞아요.
- OpenAI Official SDK — 공식 OpenAI 자바 SDK를 사용해요.
- Azure OpenAI — 마이크로소프트의 Azure SDK를 쓰고, 고급 Azure 인증을 포함한 MS 자바 스택에서 가장 잘 동작해요.
채팅 모델을 만들 때 OpenAiChatModel과 OpenAiStreamingChatModel, 그리고 Responses API용 OpenAiResponsesChatModel이 필요할 수 있어요. 각자 만드는 방법과 주요 설정을 아래에서 차례로 설명할게요.
출처: 공식문서
OpenAI 공식 문서
Maven 의존성
Plain Java
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.20.0</version>
</dependency>
Spring Boot
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot4-starter</artifactId>
<version>1.20.0-beta30</version>
</dependency>
이 starter는 Spring Boot 4가 필요해요. Spring Boot 3에서는 langchain4j-open-ai-spring-boot-starter를 쓰면 되고, 자세한 건 Spring Boot Integration 문서를 참고하세요.
API 키
OpenAI 모델을 쓰려면 API 키가 필요해요. 키는 여기에서 만들 수 있어요.
OpenAiChatModel 만들기
Plain Java
ChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
// You can also specify default chat request parameters using ChatRequestParameters or OpenAiChatRequestParameters
ChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.defaultRequestParameters(OpenAiChatRequestParameters.builder()
.modelName("gpt-4o-mini")
.build())
.build();
이렇게 하면 지정한 기본 파라미터를 가진 OpenAiChatModel 인스턴스가 만들어져요.
Spring Boot
application.properties에 추가해요:
# Mandatory properties:
langchain4j.open-ai.chat-model.api-key=${OPENAI_API_KEY}
langchain4j.open-ai.chat-model.model-name=gpt-4o-mini
# Optional properties:
langchain4j.open-ai.chat-model.base-url=...
langchain4j.open-ai.chat-model.custom-headers=...
langchain4j.open-ai.chat-model.frequency-penalty=...
langchain4j.open-ai.chat-model.log-requests=...
langchain4j.open-ai.chat-model.log-responses=...
langchain4j.open-ai.chat-model.logit-bias=...
langchain4j.open-ai.chat-model.max-retries=...
langchain4j.open-ai.chat-model.max-completion-tokens=...
langchain4j.open-ai.chat-model.max-tokens=...
langchain4j.open-ai.chat-model.metadata=...
langchain4j.open-ai.chat-model.organization-id=...
langchain4j.open-ai.chat-model.parallel-tool-calls=...
langchain4j.open-ai.chat-model.presence-penalty=...
langchain4j.open-ai.chat-model.project-id=...
langchain4j.open-ai.chat-model.reasoning-effort=...
langchain4j.open-ai.chat-model.response-format=...
langchain4j.open-ai.chat-model.return-thinking=...
langchain4j.open-ai.chat-model.seed=...
langchain4j.open-ai.chat-model.service-tier=...
langchain4j.open-ai.chat-model.stop=...
langchain4j.open-ai.chat-model.store=...
langchain4j.open-ai.chat-model.strict-schema=...
langchain4j.open-ai.chat-model.strict-tools=...
langchain4j.open-ai.chat-model.supported-capabilities=...
langchain4j.open-ai.chat-model.temperature=...
langchain4j.open-ai.chat-model.timeout=...
langchain4j.open-ai.chat-model.top-p=
langchain4j.open-ai.chat-model.user=...
# Optional Property: Custom Parameters (user-defined key=value)
langchain4j.open-ai.chat-model.custom-parameters.<key>=<value>
위 파라미터들의 설명은 대부분 여기에서 확인할 수 있어요.
이 설정은 OpenAiChatModel 빈(bean)을 만들어 줘서, AI Service로 쓰거나 필요할 때 주입해서 쓸 수 있어요. 예를 들면:
@RestController
class ChatModelController {
ChatModel chatModel;
ChatModelController(ChatModel chatModel) {
this.chatModel = chatModel;
}
@GetMapping("/model")
public String model(@RequestParam(value = "message", defaultValue = "Hello") String message) {
return chatModel.chat(message);
}
}
Structured Outputs (구조화된 출력)
Structured Outputs 기능은 tools와 response format 모두에서 지원돼요. 자세한 정보는 여기를 보세요.
툴용 Structured Outputs
툴에 Structured Outputs를 켜려면 모델을 만들 때 .strictTools(true)를 설정해요:
OpenAiChatModel.builder()
...
.strictTools(true)
.build(),
설명 한 가지 기억할 게 있는데요, 이 설정을 켜면 모든 툴 파라미터가 필수(required)가 되고 json schema의 각 object에 additionalProperties=false가 붙어요. 현재 OpenAI의 제약 때문이에요.
Response Format용 Structured Outputs
AI Services에서 응답 포맷에 Structured Outputs를 쓸 땐 .supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA)와 .strictJsonSchema(true)를 설정해요:
OpenAiChatModel.builder()
...
.supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA)
.strictJsonSchema(true)
.build();
이 경우 AI Service가 주어진 POJO에서 JSON schema를 자동으로 만들어 LLM에 전달해요.
Thinking / Reasoning
이 설정은 DeepSeek을 겨냥한 거예요.
OpenAiChatModel이나 OpenAiStreamingChatModel을 만들 때 returnThinking 파라미터를 켜면, DeepSeek API 응답의 reasoning_content 필드를 파싱해서 AiMessage.thinking()으로 돌려줘요.
OpenAiStreamingChatModel에 returnThinking이 켜져 있으면, DeepSeek API가 reasoning_content를 스트리밍할 때 StreamingChatResponseHandler.onPartialThinking()과 TokenStream.onPartialThinking() 콜백이 호출돼요.
thinking을 설정하는 예시를 볼게요:
ChatModel model = OpenAiChatModel.builder()
.baseUrl("https://api.deepseek.com/v1")
.apiKey(System.getenv("DEEPSEEK_API_KEY"))
.modelName("deepseek-reasoner")
.returnThinking(true)
.build();
sendThinking 파라미터를 켜면 AiMessage.thinking()이 DeepSeek API에 요청으로 보내져요. 필드 이름은 sendThinking(boolean, String) 빌더 메서드로 설정할 수 있고, 기본값은 reasoning_content예요.
OpenAiStreamingChatModel 만들기
Plain Java
StreamingChatModel model = OpenAiStreamingChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
// You can also specify default chat request parameters using ChatRequestParameters or OpenAiChatRequestParameters
StreamingChatModel model = OpenAiStreamingChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.defaultRequestParameters(OpenAiChatRequestParameters.builder()
.modelName("gpt-4o-mini")
.build())
.build();
Spring Boot
application.properties에 추가해요:
# Mandatory properties:
langchain4j.open-ai.streaming-chat-model.api-key=${OPENAI_API_KEY}
langchain4j.open-ai.streaming-chat-model.model-name=gpt-4o-mini
# Optional properties:
langchain4j.open-ai.streaming-chat-model.base-url=...
langchain4j.open-ai.streaming-chat-model.custom-headers=...
langchain4j.open-ai.streaming-chat-model.frequency-penalty=...
langchain4j.open-ai.streaming-chat-model.log-requests=...
langchain4j.open-ai.streaming-chat-model.log-responses=...
langchain4j.open-ai.streaming-chat-model.logit-bias=...
langchain4j.open-ai.streaming-chat-model.max-retries=...
langchain4j.open-ai.streaming-chat-model.max-completion-tokens=...
langchain4j.open-ai.streaming-chat-model.max-tokens=...
langchain4j.open-ai.streaming-chat-model.metadata=...
langchain4j.open-ai.streaming-chat-model.organization-id=...
langchain4j.open-ai.streaming-chat-model.parallel-tool-calls=...
langchain4j.open-ai.streaming-chat-model.presence-penalty=...
langchain4j.open-ai.streaming-chat-model.project-id=...
langchain4j.open-ai.streaming-chat-model.reasoning-effort=...
langchain4j.open-ai.streaming-chat-model.response-format=...
langchain4j.open-ai.streaming-chat-model.return-thinking=...
langchain4j.open-ai.streaming-chat-model.seed=...
langchain4j.open-ai.streaming-chat-model.service-tier=...
langchain4j.open-ai.streaming-chat-model.stop=...
langchain4j.open-ai.streaming-chat-model.store=...
langchain4j.open-ai.streaming-chat-model.strict-schema=...
langchain4j.open-ai.streaming-chat-model.strict-tools=...
langchain4j.open-ai.streaming-chat-model.temperature=...
langchain4j.open-ai.streaming-chat-model.timeout=...
langchain4j.open-ai.streaming-chat-model.top-p=...
langchain4j.open-ai.streaming-chat-model.user=...
# Optional Property: Custom Parameters (user-defined key=value)
langchain4j.open-ai.streaming-chat-model.custom-parameters.<key>=<value>
OpenAiModerationModel 만들기
Plain Java
ModerationModel model = OpenAiModerationModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("text-moderation-stable")
.build();
Spring Boot
application.properties에 추가해요:
# Mandatory properties:
langchain4j.open-ai.moderation-model.api-key=${OPENAI_API_KEY}
langchain4j.open-ai.moderation-model.model-name=text-moderation-stable
# Optional properties:
langchain4j.open-ai.moderation-model.base-url=...
langchain4j.open-ai.moderation-model.custom-headers=...
langchain4j.open-ai.moderation-model.log-requests=...
langchain4j.open-ai.moderation-model.log-responses=...
langchain4j.open-ai.moderation-model.max-retries=...
langchain4j.open-ai.moderation-model.organization-id=...
langchain4j.open-ai.moderation-model.project-id=...
langchain4j.open-ai.moderation-model.timeout=...
OpenAiTextToSpeechModel 만들기
OpenAiTextToSpeechModel은 OpenAI Speech API로 텍스트를 음성(TTS)으로 바꿔주고, 생성된 오디오를 Audio 객체에 담긴 raw 바이트로 돌려줘요.
지원 모델은 tts-1, tts-1-hd, gpt-4o-mini-tts, gpt-4o-mini-tts-2025-12-15(OpenAiTextToSpeechModelName 참고)이고, 기본 음성(voice)은 alloy예요.
Plain Java
import dev.langchain4j.model.audio.TextToSpeechModel;
import dev.langchain4j.model.audio.TextToSpeechRequest;
import dev.langchain4j.model.audio.TextToSpeechResponse;
import dev.langchain4j.model.openai.OpenAiTextToSpeechModel;
import dev.langchain4j.model.openai.OpenAiTextToSpeechModelName;
TextToSpeechModel model = OpenAiTextToSpeechModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(OpenAiTextToSpeechModelName.TTS_1)
.voice("alloy") // optional, defaults to "alloy"
.build();
// Convenience method (uses the model's default voice):
TextToSpeechResponse response = model.synthesize("Hello world!");
// Or with an explicit request (the voice here overrides the model default):
TextToSpeechRequest request = TextToSpeechRequest.builder()
.text("Hello world!")
.voice("nova")
.build();
TextToSpeechResponse response2 = model.synthesize(request);
byte[] audioBytes = response.audio().binaryData(); // e.g. write to an .mp3 file
String mimeType = response.audio().mimeType(); // e.g. "audio/mpeg"
입력 텍스트는 4096자를 넘으면 안 돼요(OpenAI Speech API 제한). 더 긴 입력은 IllegalArgumentException을 던져요.
OpenAiTokenCountEstimator 만들기
TokenCountEstimator tokenCountEstimator = new OpenAiTokenCountEstimator("gpt-4o-mini");
커스텀 채팅 요청 파라미터 설정
OpenAiChatModel·OpenAiStreamingChatModel을 쓸 때 HTTP 요청 JSON 본문에 채팅 요청용 커스텀 파라미터를 넣을 수 있어요. 웹 검색을 켜는 예시를 보면:
record ApproximateLocation(String city) {}
record UserLocation(String type, ApproximateLocation approximate) {}
record WebSearchOptions(UserLocation user_location) {}
WebSearchOptions webSearchOptions = new WebSearchOptions(new UserLocation("approximate", new ApproximateLocation("London")));
Map<String, Object> customParameters = Map.of("web_search_options", webSearchOptions);
ChatRequest chatRequest = ChatRequest.builder()
.messages(UserMessage.from("Where can I buy good coffee?"))
.parameters(OpenAiChatRequestParameters.builder()
.modelName("gpt-4o-mini-search-preview")
.customParameters(customParameters)
.build())
.build();
ChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.logRequests(true)
.build();
ChatResponse chatResponse = model.chat(chatRequest);
이건 다음과 같은 HTTP 요청 본문을 만들어요:
{
"model" : "gpt-4o-mini-search-preview",
"messages" : [ {
"role" : "user",
"content" : "Where can I buy good coffee?"
} ],
"web_search_options" : {
"user_location" : {
"type" : "approximate",
"approximate" : {
"city" : "London"
}
}
}
}
커스텀 파라미터는 중첩 맵 구조로도 지정할 수 있어요:
Map<String, Object> customParameters = Map.of(
"web_search_options", Map.of(
"user_location", Map.of(
"type", "approximate",
"approximate", Map.of("city", "London")
)
)
);
프롬프트 캐싱 (Prompt Caching)
OpenAI는 길고 반복되는 프롬프트 프리픽스를 캐시하고, 캐시 읽기는 입력 요금의 일부만 청구해요. 아래 제어는 OpenAiChatModel/OpenAiStreamingChatModel(Chat Completions API)과 OpenAiResponsesChatModel/OpenAiResponsesStreamingChatModel(Responses API) 양쪽 모두에서 동작해요.
프롬프트 캐싱에 대한 자세한 내용은 여기를 참고하세요.
promptCacheKey
promptCacheKey는 관련 요청들이 캐시 항목을 가진 머신에 도달할 가능성을 높이도록 라우팅을 유도하는 선택적 문자열이에요. 요청을 특정 머신에 고정시키지도, 캐시 히트를 보장하지도 않아요.
OpenAiChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-5.6")
.promptCacheKey("satisfaction_judge_v1")
.build();
promptCacheOptions
gpt-5.6 이후 모델은 캐시된 프리픽스를 breakpoint에서 정확히 매칭하고, 더 짧은 마킹 없는 프리픽스로 폴백하지 않아요. promptCacheOptions는 breakpoint가 어디서 오는지를 제어해요.
implicit— OpenAI가 가장 최근의 적격 메시지 끝에 breakpoint를 둬요.explicit— 직접 설정한 breakpoint만 사용하고, breakpoint가 없으면 아무것도 캐시되지 않아요.
OpenAiChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-5.6")
.promptCacheOptions(OpenAiPromptCacheOptions.builder()
.mode(OpenAiPromptCacheOptions.MODE_EXPLICIT)
.ttl(OpenAiPromptCacheOptions.TTL_30M)
.build())
.build();
OpenAiPromptCacheOptions.implicit()과 OpenAiPromptCacheOptions.explicit()은 모드만 설정하는 축약형이에요.
promptCacheOptions.ttl은 gpt-5.6보다 오래된 모델에 적용되는 promptCacheRetention을 대체해요. OpenAI는 둘 다 실린 요청을 거부해요.
promptCacheBreakpoint
SystemMessage, UserMessage, ToolExecutionResultMessage 각각을 프롬프트 캐시 breakpoint로 표시할 수 있어요. 프롬프트 캐싱은 프리픽스 기반이라, breakpoint는 표시된 메시지의 마지막 content 블록에 적용돼서 그 메시지까지 포함한 전체가 캐시된 프리픽스가 돼요.
OpenAiPromptCacheBreakpoint.mark()는 메시지의 마킹된 복사본을 돌려주고 원본은 건드리지 않아요:
SystemMessage systemMessage = OpenAiPromptCacheBreakpoint.mark(SystemMessage.from(SHARED_INSTRUCTIONS));
UserMessage userMessage = OpenAiPromptCacheBreakpoint.mark(UserMessage.from(LONG_DOCUMENT));
ToolExecutionResultMessage toolResult = OpenAiPromptCacheBreakpoint.mark(someToolExecutionResultMessage);
마킹은 사실 메시지의 속성일 뿐이라, 어차피 메시지를 만들고 있다면 손으로도 할 수 있어요:
SystemMessage systemMessage = SystemMessage.builder()
.text(SHARED_INSTRUCTIONS)
.attributes(Map.of(OpenAiPromptCacheBreakpoint.ATTRIBUTE_KEY,
OpenAiPromptCacheBreakpoint.MODE_EXPLICIT))
.build();
메시지를 마킹하면 LangChain4j가 그 메시지의 내용을 content 블록 리스트로 보내는데, 일반 문자열 형태는 breakpoint를 담을 수 없기 때문이에요.
AiMessage는 breakpoint를 담을 수 없어요. 어시스턴트 출력 블록은 OpenAI가 breakpoint를 허용하는 블록 타입이 아니거든요. AiMessage를 마킹하거나 explicit 외의 모드를 쓰면 HTTP 400 대신 즉시 실패해요.
요청마다 최대 4개의 캐시 쓰기를 지원하고, 그중 하나는 implicit 모드가 가져가요.
캐시 토큰 수 읽기
OpenAiTokenUsage.InputTokensDetails는 프롬프트 캐시에서 읽고 쓴 입력 토큰 수를 보고해요.
OpenAiTokenUsage tokenUsage = (OpenAiTokenUsage) chatResponse.tokenUsage();
tokenUsage.inputTokensDetails().cachedTokens(); // tokens read from the cache
tokenUsage.inputTokensDetails().cacheWriteTokens(); // tokens written to the cache
cacheWriteTokens()는 모델 제공자가 보고하지 않으면 null을 돌려주는데, 보고된 0과는 구분돼요.
raw HTTP 응답과 Server-Sent Events(SSE) 접근
OpenAiChatModel을 쓸 때 raw HTTP 응답에 접근할 수 있어요:
SuccessfulHttpResponse rawHttpResponse = ((OpenAiChatResponseMetadata) chatResponse.metadata()).rawHttpResponse();
System.out.println(rawHttpResponse.body());
System.out.println(rawHttpResponse.headers());
System.out.println(rawHttpResponse.statusCode());
OpenAiStreamingChatModel을 쓸 때는 raw HTTP 응답(위 참고)과 raw Server-Sent Events에 접근할 수 있어요:
List<ServerSentEvent> rawServerSentEvents = ((OpenAiChatResponseMetadata) chatResponse.metadata()).rawServerSentEvents();
System.out.println(rawServerSentEvents.get(0).data());
System.out.println(rawServerSentEvents.get(0).event());
HTTP 클라이언트
Plain Java
langchain4j-open-ai 모듈을 쓰면 기본 HTTP 클라이언트로 JDK의 java.net.http.HttpClient를 사용해요. 커스터마이즈하거나 다른 HTTP 클라이언트를 쓸 수도 있어요. 자세한 건 여기를 참고하세요.
Spring Boot
langchain4j-open-ai-spring-boot4-starter/langchain4j-open-ai-spring-boot-starter를 쓰면 기본 HTTP 클라이언트로 Spring의 RestClient를 사용해요. 커스터마이즈하거나 다른 HTTP 클라이언트를 쓸 수도 있어요. 자세한 건 여기를 참고하세요.
OpenAI Responses API
:::note 이 기능은 실험적이고 향후 릴리스에서 바뀔 수 있어요. :::
OpenAI의 Responses API(/v1/responses)는 Chat Completions API의 대안이에요.
OpenAiResponsesChatModel 만들기
ChatModel model = OpenAiResponsesChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-5.4")
.build();
OpenAiResponsesStreamingChatModel 만들기
StreamingChatModel model = OpenAiResponsesStreamingChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
커스텀 HTTP 헤더
인증 프록시나 게이트웨이를 거쳐 OpenAI API에 도달하고 추가 HTTP 헤더가 필요하다면, 이 헤더들을 빌더에 설정할 수 있어요. 모든 요청에 함께 보내져요:
ChatModel model = OpenAiResponsesChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.customHeaders(Map.of("Proxy-Authorization", "Basic dXNlcjpwYXNz"))
.build();
헤더 값이 상수가 아니면(예: 만료돼서 갱신해야 하는 OAuth2 토큰) Supplier를 대신 제공할 수 있어요. 매 요청 전에 호출돼요:
ChatModel model = OpenAiResponsesChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.customHeaders(() -> Map.of("Authorization", "Bearer " + tokenProvider.currentToken()))
.build();
커스텀 헤더는 마지막에 적용되므로 LangChain4j가 기본으로 설정하는 헤더(예: Authorization)를 덮어쓰는 데도 쓸 수 있어요. OpenAiResponsesStreamingChatModel에도 똑같이 적용돼요.
OpenAiResponsesChatRequestParameters
OpenAiResponsesChatRequestParameters는 DefaultChatRequestParameters를 상속받아 Responses API 전용 필드를 추가해요: previousResponseId, maxToolCalls, parallelToolCalls, topLogprobs, truncation, include, serviceTier, safetyIdentifier, promptCacheKey, promptCacheRetention, promptCacheOptions, reasoningEffort, reasoningSummary, textVerbosity, streamIncludeObfuscation, store, strictTools, strictJsonSchema.
이 파라미터들은 모델 생성 시 기본값으로 설정하거나(defaultRequestParameters), ChatRequest로 요청마다 전달할 수 있어요(요청별 파라미터가 기본값을 덮어씀):
ChatRequest chatRequest = ChatRequest.builder()
.messages(UserMessage.from("Hello"))
.parameters(OpenAiResponsesChatRequestParameters.builder()
.modelName("gpt-4o-mini")
.previousResponseId("resp_abc123")
.store(true)
.build())
.build();
기본 제공 / 서버 툴 구성
OpenAI Responses API 통합은 serverTools를 통해 OpenAI 내장 툴을 지원해요.
serverTools는 OpenAI 내장 툴을 raw OpenAI 형태 그대로 쓸 때 사용해요.
web_search, file_search 같은 OpenAI Responses API 툴 객체를 추가 타입 래퍼 없이 보내고 싶을 때 serverTools를 쓰면 돼요:
ChatModel model = OpenAiResponsesChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-5.4")
.serverTools(List.of(
Map.of(
"type", "web_search",
"filters", Map.of("allowed_domains", List.of("openai.com", "developers.openai.com")),
"user_location", Map.of(
"type", "approximate",
"country", "US")),
Map.of(
"type", "file_search",
"vector_store_ids", List.of("vs_abc123"),
"max_num_results", 3,
"filters", Map.of(
"type", "eq",
"key", "category",
"value", "blog"))))
.build();
내장 툴을 요청별로 구성할 수도 있어요:
ChatRequest chatRequest = ChatRequest.builder()
.messages(UserMessage.from("What's the weather in Berlin?"))
.parameters(OpenAiResponsesChatRequestParameters.builder()
.serverTools(List.of(Map.of("type", "web_search")))
.build())
.build();
ChatResponse response = model.chat(chatRequest);
serverTools는 모델 빌더에 기본값으로도, OpenAiResponsesChatRequestParameters로 요청별로도 설정할 수 있어요. 둘 다 제공되면 요청별 값이 우선하고 그 요청의 모델 레벨 serverTools를 대체해요.
serverTools는 제공자 특화적이고 OpenAI wire 포맷을 의도적으로 미러링하므로, 중첩 툴 필드는 일반 Map/List 값으로 제공해야 해요.
Thinking / Reasoning
OpenAI reasoning 모델(예: gpt-5.4, gpt-5-mini)은 모델 내부 reasoning의 요약을 노출하는 reasoning summaries를 지원해요.
reasoning summary를 켜려면 빌더에 reasoningSummary를 "auto"로 설정해요(또는 OpenAiResponsesChatRequestParameters로). reasoningEffort로 모델이 reasoning에 쏟는 노력을 조절할 수도 있어요.
ChatModel model = OpenAiResponsesChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-5-mini")
.reasoningEffort("low")
.reasoningSummary("auto")
.build();
ChatResponse response = model.chat("What is the capital of Germany?");
response.aiMessage().text(); // "The capital of Germany is Berlin."
response.aiMessage().thinking(); // reasoning summary text
OpenAiResponsesStreamingChatModel에 reasoningSummary가 설정되면, reasoning summary 토큰이 스트리밍될 때 StreamingChatResponseHandler.onPartialThinking() 콜백이 호출돼요:
StreamingChatModel model = OpenAiResponsesStreamingChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-5-mini")
.reasoningEffort("low")
.reasoningSummary("auto")
.build();
AiMessage.thinking()의 reasoning summary는 정보 제공용이라 후속 요청에 다시 보낼 필요가 없어요. OpenAI가 턴 사이에 버리거든요. 턴을 넘어(예: 툴 호출 사이) 모델의 reasoning 상태를 실제로 보존하려면 아래에서 설명하는 암호화 reasoning을 쓰면 돼요.
암호화 Reasoning (Reasoning을 컨텍스트에 유지하기)
store가 false(기본값)이거나 조직이 데이터 보존 정책이 0이면, 모델의 reasoning 컨텍스트가 턴 사이에 사라져요. 이걸 보존하려면 include 파라미터로 encrypted reasoning content를 요청해요:
ChatModel model = OpenAiResponsesChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-5-mini")
.reasoningEffort("medium")
.include(List.of("reasoning.encrypted_content"))
.build();
include에 "reasoning.encrypted_content"가 있으면, 응답의 reasoning 항목에 불투명한 암호화 블롭이 들어가요. 이 값은 AiMessage.attributes()에 "encrypted_reasoning" 키로 자동 저장돼요.
그 AiMessage를 후속 요청(예: 툴 호출 후)에 다시 넘기면, 암호화 reasoning이 자동으로 요청에 포함돼서 모델이 reasoning 컨텍스트를 이어나갈 수 있어요:
// Turn 1: model calls a tool
ChatResponse response1 = model.chat(ChatRequest.builder()
.messages(userMessage)
.parameters(ChatRequestParameters.builder()
.toolSpecifications(weatherTool)
.build())
.build());
AiMessage aiMessage1 = response1.aiMessage();
// aiMessage1.attribute("encrypted_reasoning", String.class) is not null
// Turn 2: send tool result back — encrypted reasoning is sent automatically
ChatResponse response2 = model.chat(ChatRequest.builder()
.messages(
userMessage,
aiMessage1, // contains encrypted reasoning in attributes
ToolExecutionResultMessage.from(aiMessage1.toolExecutionRequests().get(0), "sunny"))
.parameters(ChatRequestParameters.builder()
.toolSpecifications(weatherTool)
.build())
.build());
이 동작은 OpenAiResponsesStreamingChatModel에서도 동일해요.
OpenAiResponsesChatResponseMetadata
Responses API의 응답 메타데이터는 표준 ChatResponseMetadata에 추가 필드를 제공해요:
OpenAiResponsesChatResponseMetadata metadata =
(OpenAiResponsesChatResponseMetadata) chatResponse.metadata();
metadata.id(); // Response ID (can be used as previousResponseId)
metadata.modelName(); // Model name used for the request
metadata.finishReason(); // Finish reason (STOP, LENGTH, TOOL_EXECUTION, CONTENT_FILTER, OTHER)
metadata.tokenUsage(); // Returns OpenAiTokenUsage with detailed token counts
metadata.createdAt(); // Timestamp when the response was created
metadata.completedAt(); // Timestamp when the response was completed
metadata.serviceTier(); // Service tier used for the request
// Raw HTTP access (same as Chat Completions API)
metadata.rawHttpResponse();
metadata.rawServerSentEvents();