9:25 학원 도착
<9:30 1교시>
어제 하던거. 포스트맨으로 내 API 키에 요청하기.
근데 난 API 발급을 안받기로 했으니까 그냥 하는거 봐야징.
벡터 데이터베이스가 요즘 트렌드라고 한다. RDBMS 는 격자형 데이터베이스.
이미지의 경우 이미지의 각 요소를 벡터 요소로 저장해서, 이미지 검색할 때 예를 들어 꽃이 포함된 이미지들을 셀렉트해줘라고 하면 셀렉트 할 수 있는 형태라고 한다.
챗 지피티에게도 물어보자(그리고 주식 시장에서 알아보기 차원에서 회사도 ㅋ)
벡터 데이터베이스(Vector Database)는 고차원 벡터 데이터를 효율적으로 저장하고 검색하는 데 특화된 데이터베이스입니다. 이러한 벡터는 보통 머신러닝 모델이나 자연어 처리(NLP)에서 생성되는 임베딩(embedding) 형태의 데이터로, 텍스트, 이미지, 오디오 등 다양한 형태의 데이터를 수치적인 벡터로 변환한 것입니다. 벡터 데이터베이스는 이러한 벡터들 간의 유사도 비교를 빠르게 처리할 수 있는 기능을 제공합니다.
벡터 데이터베이스의 주요 용도는 유사도 검색(similarity search)입니다. 예를 들어, 특정 텍스트를 벡터로 변환하여, 그와 유사한 텍스트나 데이터를 빠르게 찾는 작업을 할 수 있습니다.
벡터 데이터베이스의 주요 기능:
- 고차원 벡터 처리: 벡터는 대개 고차원 데이터를 다루므로, 효율적인 검색을 위해 차원의 저주(curse of dimensionality)를 해결하는 알고리즘이 필요합니다.
- 근사 최근접 이웃 검색(ANN, Approximate Nearest Neighbor): 벡터 간의 유사도를 측정하여 가장 가까운 벡터를 찾는 기술입니다. 이는 검색 속도를 향상시키기 위해 정확도보다 속도를 우선시하는 방식으로 구현됩니다.
- 스케일링: 데이터가 매우 커질 수 있기 때문에, 분산처리와 같은 기술을 사용하여 대규모 데이터를 처리합니다.
벡터 데이터베이스를 제공하는 주요 회사들:
- Pinecone
Pinecone은 고성능 벡터 검색 엔진으로, 실시간 유사도 검색 및 추천 시스템을 위한 서비스입니다. 고차원 데이터의 빠른 검색을 위해 최적화되어 있습니다. - Weaviate
Weaviate는 오픈소스 벡터 데이터베이스로, AI와 머신러닝 프로젝트에서 벡터 데이터를 관리하고 검색하는 데 특화되어 있습니다. Weaviate는 RESTful API와 GraphQL API를 지원하여 다양한 환경에서 유용하게 사용할 수 있습니다. - Vespa
Vespa는 Yahoo에서 개발한 오픈소스 플랫폼으로, 벡터 검색 기능을 제공하며, 대규모 데이터셋에 대한 실시간 검색을 지원합니다. - Milvus
Milvus는 인기 있는 오픈소스 벡터 데이터베이스로, 대규모 벡터 데이터를 실시간으로 검색하고 분석할 수 있는 기능을 제공합니다. 벡터 검색뿐만 아니라 다양한 AI 기반 응용 프로그램에 사용될 수 있습니다. - Faiss (Facebook AI Similarity Search)
Faiss는 Facebook AI Research에서 개발한 라이브러리로, 고차원 벡터 검색에 최적화되어 있으며, 주로 벡터의 유사도를 빠르게 계산하는 데 사용됩니다. 이는 오픈소스로 제공되며, 다양한 벡터 데이터베이스에 통합될 수 있습니다. - Qdrant
Qdrant는 실시간 벡터 검색을 위한 오픈소스 벡터 데이터베이스로, AI 및 머신러닝 기반 추천 시스템에 활용될 수 있습니다. 또한, Qdrant는 RESTful API와 WebSocket을 통해 벡터 데이터를 실시간으로 처리할 수 있습니다. - Zilliz
Zilliz는 Milvus 프로젝트의 개발팀으로, 벡터 검색 기술을 제공하는 기업입니다. Milvus 외에도 다양한 AI 솔루션을 제공하고 있습니다. - Chroma
Chroma는 AI 모델에서 생성된 벡터를 관리하고 검색하는 데 사용되는 벡터 데이터베이스입니다. 이를 통해 빠르고 효율적인 텍스트 및 이미지 검색을 가능하게 합니다.
이 회사들은 벡터 데이터베이스를 활용하여 검색, 추천 시스템, 자연어 처리, 이미지 분석 등 다양한 AI 기반 응용 프로그램에서 중요한 역할을 하고 있습니다. 벡터 데이터베이스를 통해 고차원 벡터 간의 유사도를 빠르게 계산하고, 대규모 데이터셋에서 실시간으로 검색할 수 있는 기능을 제공합니다.
<10:30 2교시 ~ 4교시>
AI 사용해서 응답하는 기능 마무리했음. 나는 물론 API 키를 받지 않은 상태여서 기능은 안함.
package com.example.spring16.controller;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.example.spring16.service.GeminiService;
import com.example.spring16.service.GeminiService2;
import reactor.core.publisher.Mono;
// [Mono<T> 객체에 대하여]
// - 비동기 동작을 지원하는 객체이다
// - 컨트롤러에서 아직 동작이 완료되지 않은 Mono 객체를 리턴한다
// - 리턴된 Mono 객체를 Spring WebFlux가 받아서 동작이 완료되면 결과를 클라이언트에게 응답한다. <-dependency의 webflux 덕분에 사용
@RestController
public class GeminiController {
@Autowired GeminiService service;
@Autowired GeminiService2 service2;
// Post 방식 /ask 요청을 하면서 아래의 형식과 같은 json 문자열을 전송하면 된다.
// {"prompt":"질문"}
@PostMapping("/ask")
public Mono<String> ask(@RequestBody Map<String, String> request){
//질문 얻어내기
String prompt = request.get("prompt");
//서비스를 이용해서 질문에 대한 답을 리턴한다
return service.getChatResponse(prompt);
}
@PostMapping("/food")
public Mono<String> food(@RequestBody Map<String, String> request){
//질문 얻어내기
String prompt = request.get("prompt");
//서비스를 이용해서 질문에 대한 답을 리턴한다.
return service.getFoodCategory(prompt);
}
@PostMapping("/ask2")
public String ask2(@RequestBody Map<String, String> request){
//질문 얻어내기
String prompt = request.get("prompt");
//서비스를 이용해서 질문에 대한 답을 리턴한다.
return service2.getChatResponseSync(prompt);
}
}
package com.example.spring16.service;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import reactor.core.publisher.Mono;
@Service
public class GeminiService {
// gemini api 키를 custom.properties 파일에서 읽어오기
@Value("${gemini.key}") private String apiKey;
// google ai에 요청할 클라이언트 객체
WebClient webClient;
// 생성자
public GeminiService(WebClient.Builder builder) {
this.webClient = builder.baseUrl("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash")
.build();
}
// 질문을 던지면 Mono<String>객체를 리턴하는 메소드
public Mono<String> getChatResponse(String prompt) {
// 요청의 body 구성하기
Map<String, Object> reqeustBody = Map.of("contents", List.of(Map.of("parts", List.of(Map.of("text", prompt))))); // 생김새는
// 마치
// {"contents":[{"parts":["text":"질문"}]}]}
// 형태
Mono<String> mono = webClient.post()
.uri(uriBuilder -> uriBuilder.path(":generateContent").queryParam("key", apiKey).build())
.contentType(MediaType.APPLICATION_JSON).bodyValue(reqeustBody).retrieve().bodyToMono(String.class)
.doOnNext(responseBody -> System.out.println(responseBody)).flatMap(responseBody -> {
try {
return Mono.just(extractResponse(responseBody));
} catch (Exception e) {
return Mono.error(new RuntimeException("JSON 파싱 오류", e));
}
});
System.out.println("서비스 메소드가 리턴됨");
return mono;
}
private final Gson gson = new Gson();
//json으로 응답된 내용을 추출 및 병합해서 하나의 String 으로 얻어내는 유틸 메소드
private String extractResponse(String responseJson) {
try {
GeminiResponse geminiResponse = gson.fromJson(responseJson, GeminiResponse.class);
if (geminiResponse.getCandidates() != null && !geminiResponse.getCandidates().isEmpty()) {
GeminiResponse.Candidate firstCandidate = geminiResponse.getCandidates().get(0);
if (firstCandidate.getContent() != null && firstCandidate.getContent().getParts() != null) {
return firstCandidate.getContent().getParts().stream().map(GeminiResponse.Part::getText)
.reduce((a, b) -> a + "\n" + b) // 여러 개의 응답을 합침
.orElse("응답 없음");
}
}
} catch (JsonSyntaxException e) {
return "JSON 파싱 오류: " + e.getMessage();
}
return "응답 없음";
}
/*
* 클라이언트가 입력한 내용을 이용해서 Gemini 에 질문할 새로운 질문을 만들어낸다.
*
* 대답의 형식을 구체적으로 제한한다.
*/
public Mono<String> getFoodCategory(String food){
/*
String str = """
클라이언트가 입력한 음식: "%s"
해당 음식의 카테고리를 반환해줘.
반환할 문자열은 ["한식","중식","일식","양식","기타"] 중에서 하나만 반환해야 해.
다른 설명 없이 오직 카테고리 이름만 반환해.
""".formatted(food);
*/
String str = """
클라이언트가 입력한 음식: "%s"
해당 음식의 카테고리를 JSON 형식으로 반환해.
응답은 아래 형식을 따라야 해:
{ "category": "한식" }
["한식", "중식", "일식", "양식", "기타"] 중 하나만 "category" 값으로 넣어줘.
설명 없이 JSON 객체만 반환해.
markdown 형식으로 응답하면 안되.
""".formatted(food);
return getChatResponse(str);
}
}
package com.example.spring16.service;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import reactor.core.publisher.Mono;
@Service
public class GeminiService2 {
private final WebClient webClient;
private final Gson gson = new Gson();
private final String apiKey;
private final String url;
//생성자에 필요한 data 3 개 전달받기
public GeminiService2(WebClient.Builder builder,
@Value("${gemini.key}") String apiKey,
@Value("${gemini.url}") String url //서비스 객체가 생성되는 시점에 @Value 가 읽어와 지지 않는다.
) {
//전달 받은 내용을 필드에 저장하기
this.apiKey=apiKey;
this.url=url;
this.webClient=builder.baseUrl(url).build();
}
public Mono<String> getFoodCategory(String food) {
String str = """
클라이언트가 입력한 음식: "%s"
해당 음식의 카테고리를 JSON 형식으로 반환해.
응답은 아래 형식을 따라야 해:
{ "category": "한식" }
["한식", "중식", "일식", "양식", "기타"] 중 하나만 "category" 값으로 넣어줘.
설명 없이 JSON 객체만 반환해.
markdown 형식으로 응답하면 안되.
""".formatted(food);
return getChatResponse(str);
}
public String getChatResponseSync(String prompt) {
//GeminiRequest 구성하기
GeminiRequest request = new GeminiRequest();
GeminiRequest.Content content = new GeminiRequest.Content();
GeminiRequest.Part part = new GeminiRequest.Part();
//part 에 질문을 담는다.
part.setText(prompt);
content.setParts(List.of(part));
request.setContents(List.of(content));
String result = webClient.post()
.uri(uriBuilder -> uriBuilder.path(":generateContent")
.queryParam("key", apiKey)
.build())
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request) // Map 객체 대신에 GeminiRequest 객체를 넣어주면 json 으로 변경된다.
.retrieve()
.bodyToMono(String.class)
.doOnNext(responseBody -> System.out.println(responseBody))
.flatMap(responseBody -> {
try {
return Mono.just(extractResponse(responseBody));
} catch (Exception e) {
return Mono.error(new RuntimeException("JSON 파싱 오류", e));
}
}).block();//block 메소드를 호출하면 결과 문자열을 받아올때까지 기다린다(굳이 비동기 동작이 필요없다면)
return result;
}
// 질문을 던지면 Mono<String>객체를 리턴하는 메소드
public Mono<String> getChatResponse(String prompt) {
// 요청의 body 구성하기
Map<String, Object> reqeustBody = Map.of("contents", List.of(Map.of("parts", List.of(Map.of("text", prompt))))); // 생김새는
// 마치
// {"contents":[{"parts":["text":"질문"}]}]}
// 형태
Mono<String> mono = webClient.post()
.uri(uriBuilder -> uriBuilder.path(":generateContent").queryParam("key", apiKey).build())
.contentType(MediaType.APPLICATION_JSON).bodyValue(reqeustBody).retrieve().bodyToMono(String.class)
.doOnNext(responseBody -> System.out.println(responseBody)).flatMap(responseBody -> {
try {
return Mono.just(extractResponse(responseBody));
} catch (Exception e) {
return Mono.error(new RuntimeException("JSON 파싱 오류", e));
}
});
System.out.println("서비스 메소드가 리턴됨");
return mono;
}
//응답된 json 을 파싱해서 문자열 얻어내는 메소드
private String extractResponse(String responseJson) {
try {
GeminiResponse geminiResponse = gson.fromJson(responseJson, GeminiResponse.class);
return Optional.ofNullable(geminiResponse)
.map(GeminiResponse::getCandidates)
.filter(candidates -> !candidates.isEmpty())
.map(candidates -> candidates.get(0))
.map(GeminiResponse.Candidate::getContent)
.map(GeminiResponse.Content::getParts)
.filter(parts -> !parts.isEmpty())
.map(parts -> parts.stream().map(GeminiResponse.Part::getText).collect(Collectors.joining("\n")))
.orElse("응답 없음");
} catch (JsonSyntaxException e) {
return "JSON 파싱 오류: " + e.getMessage();
}
}
}
service는 비동기 방식, service2는 동기 방식이라고 하심. 이김에 챗지피티에게 한 번 더물어보기.
동기 방식과 비동기 방식은 프로그램이나 시스템에서 작업을 처리하는 방식에 따라 차이를 두는 개념입니다. 간단히 말해, 두 방식은 작업 처리 방식의 순서와 기다림에 따라 구분됩니다.
1. 동기 방식 (Synchronous)
동기 방식은 작업을 순차적으로 처리하는 방식입니다. 하나의 작업이 끝나야 그 다음 작업을 시작할 수 있습니다. 즉, 작업이 완료될 때까지 기다린 후, 그 결과를 받아서 다음 작업을 진행합니다.
- 예시:
- 밥을 지을 때, 물이 끓을 때까지 기다리고, 끓은 물에 밥을 넣고 다시 밥이 다 될 때까지 기다린다.
- 특징:
- 작업 순서가 정해져 있고, 이전 작업이 완료될 때까지 다음 작업을 시작할 수 없다.
- 프로그램에서 다른 작업을 동시에 처리하지 못하고, 하나씩 처리된다.
2. 비동기 방식 (Asynchronous)
비동기 방식은 작업을 동시에 처리할 수 있는 방식입니다. 작업을 요청한 후, 그 작업이 완료될 때까지 기다리지 않고 다른 작업을 먼저 처리하며, 작업이 끝나면 결과를 받아옵니다.
- 예시:
- 밥을 짓는 동안 다른 일을 할 수 있다. 예를 들어, 밥을 짓고 있는 동안 다른 요리를 하거나 청소를 한다. 밥이 다 되면 알림이 오거나 밥을 확인할 수 있다.
- 특징:
- 작업을 요청하고 그 결과를 기다리는 동안 다른 일을 동시에 할 수 있다.
- 작업이 끝났을 때 알림을 받거나 결과를 받아와서 후속 작업을 진행한다.
요약
- 동기 방식: 순차적으로 처리, 하나의 작업이 끝날 때까지 기다려야 한다.
- 비동기 방식: 동시에 여러 작업을 처리, 하나의 작업을 기다리지 않고 다른 작업을 진행한다.
프로그래밍에서는 비동기 방식이 주로 I/O 작업(파일 읽기/쓰기, 네트워크 요청 등)을 처리할 때 많이 사용되며, 프로그램의 효율성을 높이는 데 도움을 줍니다.
<14:30 5교시>
리액트로 퀴즈 풀어서 제출하면 AI가 풀이를 채점해주는 프로그램을 만들려고 한다.
안하려고 했었는데 다른 사람들 하는거 보니까 재밌어 보여서 해보고 싶어지긴 한데... API 무료 버전 쓰는게 영 맘에 걸린다. 자주 안쓰는 구글 계정으로 해보던가 해야겠다.
<15:30 6교시>
AI에게 어떻게 질문할지를 작성하는 프로그래밍을 프롬프트 엔지니어링이라고 한다.
이 질문이면 될 것 같은데, API를 연결하지 않아서 되는지는 모르겠당 ..ㅎㅎ
<16:30 7교시>
체점 결과를 퀴즈 화면에 나오게 하기.
<17:30 8교시> 대학원 수업 참여로 인한 조퇴
'자바풀스택 과정 > 자바 풀 스택 : 수업내용정리' 카테고리의 다른 글
자바 풀 스택 3/24 하루 기록 080 (0) | 2025.03.24 |
---|---|
자바 풀 스택 3/21 하루 기록 079 (0) | 2025.03.21 |
자바 풀 스택 3/19 하루 기록 077 (0) | 2025.03.19 |
자바 풀 스택 3/18 하루 기록 076 (0) | 2025.03.18 |
자바 풀 스택 3/17 하루 기록 075 (0) | 2025.03.17 |