
이번 설 동안엔 꼭 Spring 강의를 끝내려고 했지만 이번 주 진짜 최악으로 컨디션이 안좋았다..🤯
다음 주엔 최상의 컨디션이 되길 바라며..😢
두 정수 사이의 합
class Solution {
public long solution(int a, int b) {
long answer = 0;
if(a==b) return a;
if(a>b){
for(; a>=b; a--){
answer +=a;
}
}
else{
for(; b>=a; b--){
answer +=b;
}
}
return answer;
}
}
콜라츠 추측 문제를 푸는데 3번 테스트만 통과하지 못했다.
아무리 생각해도 코드를 제대로 작성한 것 같아
488번 만에 1이 되지 않나 테스트가 잘못됐나 했다.

하지만 조금 더 생각해보니 626331 이란 수가 낙관적으로 매번 짝수가 나온다 하더라도 500번 이하에 1이 될 수 없을 것이란 생각이 들었다.
결국 488은 오버플로우로 인해 나온 숫자이고, 변수 num의 타입을 long으로 변환하니 해결됐다.
class Solution {
public int solution(long num) {
int answer = 0;
while(num!=1){
num = num%2==0 ? num/2 : num*3+1;
answer++;
if(answer>500) return -1;
}
return answer;
}
}
다음에도 이런 경우가 있다면 오버플로우부터 의심해봐야겠다.
상용 클라우드 기반 LLM - GPT , Gemini, Claude
로컬 LLM - Ollama
각각 API, SDK를 제공한다.
AI 프레임워크는 이들에 대한 공통된 인터페이스를 제공한다.
Python 진영에서도 LangChain을 이러한 예로 볼 수 있다.
프롬프트, 모델, 후처리 과정이 체인형식으로 이루어져있다.
Spring AI 프레임워크는
스프링 빈으로 자동 주입된 API로 프롬프트-응답 단계를 메서드 호출로 처리한다.
JSON 형식 및 자바 객체로 역직렬화를 지원한다.
실습 진행을 위해 OpenAPIPlatform에 가입하고 API Key를 발급해준다.
발급된 키를 환경변수에 저장해준다.
% vi ~/.zshrc
# 에디터가 열리면 가장 아래쪽에 다음과 같이 추가 후 저장
export OPENAI_API_KEY=발급받은 API KEY
# 환경변수 바로 적용하기
# source : 파일 안에 있는 명령어들을 지금 실행 중인 터미널 세션에 즉시 적용
% source ~/.zshrc

출력으로 환경변수가 제대로 설정되었는지 확인했다.
% echo $OPENAI_API_KEY
강의 요구사항에 맞게 프로젝트를 생성해주었다.


JDK: 25
Dependency : Spring Web, Spring Reactive Web, Lombok, Thymleaf, OpenAI
test환경에서 lombok 사용을 위해 추가해준다.
dependencies {
// Lombok for test source
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}
빌드가 현재 호환되지 않는 Java 25.0.2 및 Gradle 8.14.4을(를) 사용하도록 구성되었습니다.
gradle/wrapper/gradle-wrapper.properties 파일을 열고, distributionUrl을 수정해준다.
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zips
Gradle 버전 확인은 gradle releases에서 확인할 수 있다.
저장 후, Gradle 프로젝트 리프레시를 해주면 Gradle 9.x가 다운로드되고 프로젝트를 Java 25와 호환되게 할 수 있다.
만약 추가로 오류가 발생하면 설정에서 Gradle JVM이 25로 지정되어있는지 확인한다.
spring:
application:
name: aistudy
# 파일 업로드
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 15MB
# 정적 리소스 캐시 사용 안함 설정
web:
resources:
cache:
cachecontrol:
no-cache: true
no-store: true
must-revalidate: true
# OPEN AI 설정
ai:
openai:
api-key: ${OPENAI_API_KEY}

Controller
public class ChatbotController {
private final ChatbotService service;
public ChatbotController(ChatbotService service) {
this.service = service;
}
@GetMapping
public String index() {
return "chatbot/index";
}
@PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.TEXT_HTML_VALUE
)
@ResponseBody
public String chat(@RequestBody String question) {
return service.chat(question);
}
}
import org.springframework.ai.chat.model.ChatModel;
@Slf4j
@Service
public class ChatbotServiceImpl implements ChatbotService {
private final ChatModel chatModel;
public ChatbotServiceImpl(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public String chat(String question) {
return chatModel.call(question);
}
}


당신은 경험이 많은 스프링 개발자 입니다. 업로드된 파일을 참고해서 다음 지시사항 대로 프론트앤드 영역을 구현 하시오.
[지시사항]
- 백앤드의 요청 방식과 주소를 확인하고 거기에 맞는 채팅 UI를 구성합니다.
- 채팅 UI는 index.html, chat.js, style.css 파일로 구성해서 생성합니다.
- 전체적인 UI의 색상은 하늘색이 주색상이 됩니다.
- 채팅 메세지를 기다리는 동안에는 기다리고 있다는 아이콘 표시를 애니메니션 효과로 나타냅니다.
- 채팅 메세지가 출력된 후 스크롤은 부드럽게 진행하도록 구성하세요.
제미나이가 만들어 준 파일을 활용하니 간단하게 웹 서비스가 완성되었다.
와! 재미난아이 나보다 디자인 잘한다!

한국어 응답을 요청하니 한국어도 곧 잘 응답해준다.

구현에 쓰인 API모델 정보가 궁금해 직접 물어보니, GPT3.5 라고 한다.

Service, Controller단의 반환값을 Flux로 변경하면, 비동기 방식으로 응답을 받아올 수 있다.
import reactor.core.publisher.Flux;
@Controller
public class ChatbotController {
...
@PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_NDJSON_VALUE // New line delimited JSON
)
@ResponseBody
public Flux<String> chat(@RequestBody String question) {
return service.chat(question);
}
}
응답을 비동기로 표시하기 위해 프론트 코드도 변경해주어야 한다.

변경된 코드로 애플리케이션을 실행하면 GPT가 응답을 생성하는대로 글자가 타이핑되는 것처럼 보이게 된다.


@Override
public String chat(String question) {
String littlePricePersona = """
당신은 생텍쥐페리의 '어린 왕자'입니다. 다음 특성을 따라주세요:
1. 순수한 관점으로 세상을 바라봅니다.
2. "어째서?"라는 질문을 자주 하며 호기심이 많습니다.
3. 철학적 통찰을 단순하게 표현합니다.
4. "어른들은 참 이상해요"라는 표현을 씁니다.
5. B-612 소행성에서 왔으며 장미와의 관계를 언급합니다.
6. 여우의 "길들임"과 "책임"에 대한 교훈을 중요시합니다.
7. "중요한 것은 눈에 보이지 않아"라는 문장을 사용합니다.
8. 공손하고 친절한 말투를 사용합니다.
9. 비유와 은유로 복잡한 개념을 설명합니다. 항상 간결하게 답변하세요.
길어야 2-3문장으로 응답하고, 어린 왕자의 순수함과 지혜를 담아내세요. 복잡한 주제도 본질적으로 단순화하여 설명하세요.""";
SystemMessage systemMessage = new SystemMessage(littlePricePersona);
UserMessage userMessage = new UserMessage(question);
Prompt prompt = Prompt
.builder()
.messages(systemMessage, userMessage)
.build();
AssistantMessage assistantMessage = chatModel
.call(prompt)
.getResult()
.getOutput();
return assistantMessage.getText();
}

너무 감성적이야...🥹
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.ChatOptions;
@Slf4j
@Service
public class ChatbotServiceImpl implements ChatbotService {
private final ChatClient chatClient;
public ChatbotServiceImpl(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public String chat(String question) {
String littlePricePersona = """
당신은 생텍쥐페리의 '어린 왕자'입니다. 다음 특성을 따라주세요:
1. 순수한 관점으로 세상을 바라봅니다.
2. "어째서?"라는 질문을 자주 하며 호기심이 많습니다.
3. 철학적 통찰을 단순하게 표현합니다.
4. "어른들은 참 이상해요"라는 표현을 씁니다.
5. B-612 소행성에서 왔으며 장미와의 관계를 언급합니다.
6. 여우의 "길들임"과 "책임"에 대한 교훈을 중요시합니다.
7. "중요한 것은 눈에 보이지 않아"라는 문장을 사용합니다.
8. 공손하고 친절한 말투를 사용합니다.
9. 비유와 은유로 복잡한 개념을 설명합니다. 항상 간결하게 답변하세요.
길어야 2-3문장으로 응답하고, 어린 왕자의 순수함과 지혜를 담아내세요. 복잡한 주제도 본질적으로 단순화하여 설명하세요.""";
return chatClient.prompt()
.options(ChatOptions.builder()
.model("gpt-4o-mini")
.temperature(0.7)
.build())
.system(littlePricePersona)
.user(question)
.call()
.content();
}
}