Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2735626
chore: env 파일 import
kjyyjk May 8, 2026
f75ad57
feat: 유저 챗 엔드포인트 및 ai 호출 구현
kjyyjk May 8, 2026
f6fa3c4
feat: llm에게 필요한 데이터 제공
kjyyjk May 9, 2026
d250287
refactor: builder에서 프롬프트 설정
kjyyjk May 9, 2026
4ecdb13
chore: advisors-vector-store 의존성 추가
kjyyjk May 9, 2026
4ac5a9a
refactor: 벡터 db 및 어드바이저 활용
kjyyjk May 9, 2026
46b20a7
chore: markdown-document-reader 의존성 추가
kjyyjk May 9, 2026
62b7aad
docs: resources/data에 layer_n 복사
kjyyjk May 9, 2026
28b3413
feat: md 파일 벡터 저장소에 저장
kjyyjk May 9, 2026
8759009
feat: 응답 content type json으로 변경
kjyyjk May 10, 2026
8ec9bac
refactor: RequestBody dto로 매핑
kjyyjk May 10, 2026
d2901a6
refactor: 테스트 incorrect 이유 글자수 2배 증가
kjyyjk May 10, 2026
64ee947
refactor: topk 2배 증가
kjyyjk May 10, 2026
aea6e10
feat: 디버깅용 엔드포인트 추가
kjyyjk May 11, 2026
e3fe990
docs: deprecated 문서 vector db에서 제거
kjyyjk May 11, 2026
071ec9b
refactor: advisor 커스텀 템플릿 설정
kjyyjk May 13, 2026
6eb21ee
refactor: 프롬프트 내 할루시네이션 방지 내용 추가
kjyyjk May 13, 2026
838f408
feat: 챗봇 응답에 token 정보 포함
kjyyjk May 15, 2026
162cb3c
feat: 디버깅 엔드포인트 응답에 token 정보 포함
kjyyjk May 15, 2026
ebaab45
refactor: controller 패키지 분리
kjyyjk May 15, 2026
ef3815c
refactor: service 분리
kjyyjk May 15, 2026
f8f89e7
feat: 대화 맥락 유지를 위한 chatmemory 추가
kjyyjk May 16, 2026
deb627d
feat: 챗봇 세션 시작/종료 엔드포인트 추가
kjyyjk May 16, 2026
2c6e041
feat: 챗봇 세션 시작/종료 엔드포인트 변경
kjyyjk May 16, 2026
9383c9a
refactor: config 설정 분리
kjyyjk May 16, 2026
3700df4
feat: 챗봇 ui 구현
kjyyjk May 16, 2026
53caf47
feat: 챗봇 ui 개선
kjyyjk May 16, 2026
71121c4
refactor: vip 연락수단 제거
kjyyjk May 16, 2026
5106f1d
refactor: 챗로그 청크 제거
kjyyjk May 17, 2026
ea159ca
chore: ai rag 의존성 추가
kjyyjk May 17, 2026
918edd4
refactor: qa 어드바이저 미사용 및 쿼리 확장 추가
kjyyjk May 18, 2026
d35b780
refactor: 미사용 코드 제거(디버깅 엔드포인트)
kjyyjk May 18, 2026
0d96e85
feat: 리랭크 구현
kjyyjk May 18, 2026
9f1ec40
refactor: 리랭크 수정 및 디버깅
kjyyjk May 18, 2026
f287a6d
refactor: 검색되 청크에 해당하는 문서 전체를 컨텍스트에 포함
kjyyjk May 19, 2026
24f09c9
refactor: 메모리 어드바이저 임시 제거
kjyyjk May 19, 2026
5f0b94c
refactor: 미사용 코드 제거
kjyyjk May 20, 2026
c5bb941
refactor: 주석 추가
kjyyjk May 20, 2026
165f724
docs: 벽 리포트 작성
kjyyjk May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ dependencyManagement {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.ai:spring-ai-starter-model-openai'
implementation 'org.springframework.ai:spring-ai-advisors-vector-store'
implementation 'org.springframework.ai:spring-ai-markdown-document-reader'
implementation 'org.springframework.ai:spring-ai-rag'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Expand Down
2 changes: 1 addition & 1 deletion data/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def main():
if args.verbose:
print(f"[{qid}] {marker} ({tier}) {question_ko[:40]}...")
if score == 0:
print(f" 이유: {judgment.get('reason', '')[:80]}")
print(f" 이유: {judgment.get('reason', '')[:160]}")

# 진행률 (10개마다)
if not args.verbose and (i + 1) % 10 == 0:
Expand Down
41 changes: 28 additions & 13 deletions mission/wall-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,51 @@

> 구현하면서 잘 안 됐던 것, 예상과 달랐던 것을 적어주세요.

-

- 초반 조회 결과에 질문 관련 내용이 없길래 topK 값을 2배~4배까지 늘려 해당 질문에 대한 답변을 잘할 수 있도록 개선했습니다.
하지만 그 외 다른 질문에 대한 정확도는 확연히 떨어지는 것을 발견했습니다. 컨텍스트에 많은 내용이 들어가 오히려 혼란을 준 것 같습니다.
현재 구현 기준 기본값인 topK=4가 가장 최적으로 보여 롤백했습니다.
- 네이버페이에 대해 물어봤는데 카카오페이에 대한 답변을 하는 경우가 있었습니다.
알고보니 ChatMemory 의해 이전 질문이 컨텍스트에 포함되며 이전 질문을 현재 질문이라고 착각해서 생긴 문제였습니다.
ChatMemory를 제거하고 대화 내역이 보존되지 않게 했지만 추후 해결해야할 문제입니다.

## 2. 해결하지 못한 것

> 시도했지만 결국 해결 못한 문제가 있다면 적어주세요.

-

- 컨텍스트에 질문에 대한 정답이 포함되어 있는데 틀린 답변을 내놓습니다. 관련 케이스를 살펴보니 정답 내용이 첫번째에 오지 않을 경우 주로 발생했습니다.
해결하기 위해 리랭크 개념을 학습하고 llm에게 청크 재정렬을 맡겨봤는데 이 또한 제가 의도한대로 재정렬되지 않았습니다.
- 컨텍스트에 상반된 내용이 있을 때에 따라 다른 대답을 하는 문제를 해결하지 못했습니다. 예를 들어 `VIP는 냉장 배송도 무료배송인가요?`를 물었을 때
`vip는 모두 무료다`와 `냉장 배송은 등급 상관없이 4000원 고정이다`라는 내용이 섞여 `vip는 냉장 배송도 무료다` 라는 잘못된 답변을 내놓습니다.
프롬프트에 내용이 상반될 경우 더 상세한 것, 세부적인 것을 기준으로 하라고 명시했지만 상세하고 세부적인 것의 기준 조차 애매해 잘동작하지 않습니다.
- 명확하지 않은 단어를 잘 이해하지 못하는 경우가 많습니다. 예를 들어 `비구독 상품`이라고 하면 비구독 사용자가 상품을 구매하는 경우라고 잘못 이해해 이해해
틀린 답변을 내뱉습니다.

## 3. 정확도 측정 결과

> 테스트 질문 100개로 측정한 정확도를 기록해주세요.

| 난이도 | 정확도 | 비고 |
|--------|--------|------|
| easy | | |
| medium | | |
| hard | | |
> 테스트 질문 150개로 측정한 정확도를 기록해주세요.

| 난이도 | 정확도 | 비고 |
|--------|-------|----|
| easy | 14/30 | |
| medium | 27/94 | |
| hard | 6/26 | |

## 4. 왜 그런 결과가 나왔는지

> 정확도가 낮은 난이도의 질문을 몇 개 살펴보고, 왜 틀렸는지 분석해주세요.

-


## 5. 개선하고 싶은 것

> 시간이 더 있었다면 시도해보고 싶은 개선점을 적어주세요.

-
- 답변의 말투를 개선하고 싶습니다. 현재는 같은 내용을 반복하거나, 사용자가 응?할만한 말투를 구사합니다.
또한 내부 컨텍스트 문서의 존재를 답변에 포함하기도 합니다.
- 대화 맥락을 보존해 더욱 고객 응대에 능한 챗봇을 만들고 싶습니다.
- 인메모리 외 인프라를 구축해 정말 실무의 챗봇과 유사하게 만들어보고 싶습니다.
- 평가 방식을 개선해 모델의 성능을 제대로 측정하고 반복 개선해나가보고 싶습니다. 현재 평가는 이걸 왜 틀렸다고 하는거지?싶은 부분이 많습니다.
챗봇의 목적마다 평가 방식이 다르니 제가 만들고자 하는 챗봇을 명확히 정의하고 그에 맞는 평가 방식을 새로 만들어보고 싶습니다.
- 챗 로그, 이력을 관리하고 활용해보고 싶습니다. 주어진 데이터셋의 챗 로그는 컨텍스트에 넣지 않고 가장 많이 묻는 질문과 같은 운영 통계 목적으로 활용했습니다.
다른 방향으로 활용할 수 있다면 참고해 챗 로그까지 활용해 데이터 축적에 따른 성능 향상을 이루고 싶습니다.
- 구버전 문서도 마찬가지로 필요 없다고 느껴 제거했는데 필요하다면 포함해 응답 수준을 높여보고 싶습니다.
16 changes: 16 additions & 0 deletions src/main/java/com/cholog/bootcamp/config/VectorStoreConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.cholog.bootcamp.config;

import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class VectorStoreConfig {

@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return SimpleVectorStore.builder(embeddingModel).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.cholog.bootcamp.controller;

import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ChatPageController {

@GetMapping({"/", "/chat"})
public String chat(Model model) {
model.addAttribute("pageTitle", "초록 고객지원 챗봇");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

웹 UI를 직접 구현하신 부분 너무 좋네요 👍

다만 initialMessage / quickPrompts 가 컨트롤러에 인라인 String이라, 아래와 같은 확장 시 복붙이 발생할 수 있어요.

  • 다른 채널(예: 슬랙 봇)에서 같은 "환영 메시지"를 쓰려면 코드 복붙
  • 이메일에서 "운영시간 안내" 를 같이 보내려면 또 복붙
  • i18n (영어 페이지) 추가 시 또 복붙

챗봇 본체를 채널-무관 인터페이스로 두고, 채널별 어댑터가 각자의 환영 메시지를 가져가는 형태로 한 번 그려보면 어떨까요? 예시)

interface ChatChannel {
    String welcomeMessage();
    List<String> quickPrompts();
    void send(String userId, String message);
}

model.addAttribute("initialMessage", """
안녕하세요. 초록 고객지원 챗봇입니다.🤖
무엇을 도와드릴까요?

📞 운영시간 안내
자동 챗봇은 24시간 이용하실 수 있습니다.
상담사 연결 및 전화 상담은 평일 오전 9시부터 오후 6시까지 가능하며, 주말 및 공휴일에는 운영되지 않습니다.
운영 시간 외 문의는 챗봇을 이용하시거나 이메일로 남겨주시면 다음 영업일부터 순차적으로 확인해 드리겠습니다.

☎️ 문의 전화: 1588-0000
📧 이메일: support@cholog.kr
💬 카카오톡: @초록
""");
model.addAttribute("quickPrompts", List.of(
"배송은 보통 얼마나 걸리나요?",
"반품 신청 기준을 알려주세요.",
"멤버십 등급 혜택이 궁금해요."
));
return "chat";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.cholog.bootcamp.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.cholog.bootcamp.dto.ChatbotRequest;
import com.cholog.bootcamp.dto.ChatbotResponse;
import com.cholog.bootcamp.service.ChatbotService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
@RequestMapping("/api/chat")
@RestController
public class ChatbotController {

private final ChatbotService chatbotService;

@PostMapping
public ResponseEntity<ChatbotResponse> chat(
@RequestBody ChatbotRequest request
) {
ChatbotResponse response = chatbotService.chat(request);
return ResponseEntity.ok().body(response);
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/cholog/bootcamp/dto/ChatbotRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.cholog.bootcamp.dto;

public record ChatbotRequest(
String question
) {
}
27 changes: 27 additions & 0 deletions src/main/java/com/cholog/bootcamp/dto/ChatbotResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.cholog.bootcamp.dto;

import org.springframework.ai.chat.metadata.Usage;

public record ChatbotResponse(
String answer,
TokenUsageInfo tokenUsage
) {

public static ChatbotResponse from(String answer, Usage usage) {
return new ChatbotResponse(
answer,
new TokenUsageInfo(
usage.getPromptTokens(),
usage.getCompletionTokens(),
usage.getTotalTokens()
)
);
}

private record TokenUsageInfo(
int promptTokens,
int completionTokens,
int totalTokens
) {
}
}
106 changes: 106 additions & 0 deletions src/main/java/com/cholog/bootcamp/service/ChatbotService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.cholog.bootcamp.service;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.metadata.Usage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Service;

import com.cholog.bootcamp.dto.ChatbotRequest;
import com.cholog.bootcamp.dto.ChatbotResponse;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class ChatbotService {

private final ChatClient chatClient;
private final VectorStore vectorStore;
private final ResourcePatternResolver resolver;

public ChatbotService(
VectorStore vectorStore,
MarkdownReader markdownReader,
ChatClient.Builder chatClientBuilder
) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public ChatbotService(...) {
    ...
    vectorStore.add(markdownReader.loadAll());
    ...
}

생성자에서 임베딩 호출 부수효과가 있어요. 트레이드오프:

  • 부팅 지연 — 첫 ChatbotService Bean 생성 시점에 모든 임베딩 호출
  • 테스트 어려움new ChatbotService(...) 가 매번 임베딩 호출 → OpenAI quota 소모
  • 부팅 실패 가능성 — 임베딩 API 일시 장애 시 서버가 못 뜸
  • 책임 분리 약함답변 생성임베딩 초기화 두 책임

분리 방향)

  • (a) VectorStoreInitializer implements ApplicationRunner 로 별도 컴포넌트
  • (b) @Bean ApplicationRunner 로 Config에서 분리

"생성과 초기화를 같은 자리에 두지 마라" 라는 원칙, 한 번 고민해보면 좋겠습니다~

this.chatClient = chatClientBuilder.build();
this.vectorStore = vectorStore;
vectorStore.add(markdownReader.loadAll());
this.resolver = new PathMatchingResourcePatternResolver();
}

public ChatbotResponse chat(ChatbotRequest request) {
// 검색
SearchRequest searchRequest = getSearchRequest(request.question(), 4);
List<Document> documents = vectorStore.similaritySearch(searchRequest);

// 증강 & 생성
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String answer = chatResponse.getResult().getOutput().getText();
Usage usage = chatResponse.getMetadata().getUsage();

아래 케이스에서 NPE → 사용자에게 500이 갈 가능성이 있어요.

  • chatResponse 가 null인 경우 (rate limit, network error)
  • getResult() 가 null인 경우
  • getUsage() 가 null인 경우 (provider별 가능)

방어적 코드 추가도 좋고, 더 나아가 "LLM 호출 자체가 실패했을 때 어떤 응답을 사용자에게 보낼지" 를 명시적으로 분기하는 것도 한 번 고민해보면 좋겠습니다. "일시적 장애로 답변이 어렵습니다" 같은 명시적 응답이 운영자 alert 잡는 데도 도움이 될 수 있어요!

documents = getFullDocuments(documents);
String context = getContext(documents);
ChatResponse chatResponse = chatClient.prompt()
.system("""
당신은 초록 고객센터의 챗봇입니다.
주어진 [컨텍스트]를 기반으로 [사용자 질문]에 답변해주세요.

답변 규칙
- 제공된 컨텍스트를 기반으로만 답변하세요. 절대 일반 상식으로 추론하지 마세요.
- 만약 주어진 컨텍스트로 답변할 수 없다면 모르겠다고 안내하세요.
- 내용이 충돌하는 경우 다음 우선순위를 따라 답변 합니다.
- 질문 도메인과 가장 근접한 내용
- 더 구체적인 상황을 다루는 내용
- 더 최신 버전의 내용
""")
.user("""
[사용자 질문]
%s

[컨텍스트]
%s
""".formatted(request.question(), context))
.call()
.chatResponse();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

system prompt 너무 잘 짜셨네요 👍 특히 아래 부분이 인상적이에요.

- 내용이 충돌하는 경우 다음 우선순위를 따라 답변 합니다.
    - 질문 도메인과 가장 근접한 내용
    - 더 구체적인 상황을 다루는 내용
    - 더 최신 버전의 내용

이 규칙은 코드 가 아니라 프롬프트 에 표현한 점이 좋습니다 — 정책 결정을 LLM과 공유하는 자연스러운 자리예요.

다만 위치가 인라인 String이라:

  • 운영자가 우선순위를 수정하려면 컴파일·빌드·배포
  • 우선순위 v1 vs v2 비교가 자연스럽지 않음
  • diff에서 "왜 바꿨는지" 안 보임

외부 .st 분리하는 구조로 개선해보면 어떨까요?

@Value("classpath:prompts/faq-system.st")
private Resource systemPrompt;


String answer = chatResponse.getResult().getOutput().getText();
Usage usage = chatResponse.getMetadata().getUsage();
return ChatbotResponse.from(answer, usage);
}

private List<Document> getFullDocuments(List<Document> documents) {
return documents.stream()
.map(document -> document.getMetadata().get("filename").toString())
.distinct()
.map(filename -> {
try {
return resolver.getResources("classpath:data/**/" + filename)[0];
} catch (IOException e) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분이 핵심인 것 같아서 자세히 남길게요.

return documents.stream()
    .map(document -> document.getMetadata().get("filename").toString())
    .distinct()
    .map(filename -> {
        try {
            return resolver.getResources("classpath:data/**/" + filename)[0];
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    })
    .map(TextReader::new)
    .flatMap(reader -> reader.get().stream())
    .toList();

흐름:

  1. similaritySearch가 top-4 청크 를 반환
  2. 청크에서 filename 추출 → distinct
  3. 해당 파일을 classpath에서 다시 통째로 읽음
  4. 파일 전체를 LLM 컨텍스트에 보냄

즉 검색은 "어떤 파일이 관련 있는가" 까지만 알려주고, LLM 입력은 4개 파일 전체가 됩니다.

문제:

  • 청킹 효과 무효화 — splitter로 작은 청크를 만든 의미가 사라짐
  • 토큰 폭발return-policy-v3.md 60줄 × 4파일 = 200+줄을 매번 LLM에 보냄
  • 위치 효과 손실 — LLM은 입력의 시작과 끝을 더 강하게 참조하는데 (중간이 묻힘), 정책 본문 절반이 중간에 묻힐 가능성
  • 비용 — input token 청구 폭증

에 대해 고민해보면 좋겠습니다!

throw new RuntimeException(e);
}
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정보 없는 throw 안티패턴이 보이네요.

throw new RuntimeException(e);

운영 시점에 이 코드가 발화하면 아래와 같은 문제가 있습니다.

  • 어떤 filename이 실패했는지 메시지에 없음 → 디버깅 위해 stack trace + 코드 추적 필요
  • 사용자에게는 500 에러만 가고 왜 실패했는지 알 길 없음

최소한 아래와 같이 정보를 담아주면 좋겠습니다.

throw new IllegalStateException("문서 로드 실패: " + filename, e);

에러 메시지도 의미 있는 정보 여야 한다는 원칙, 한 번 참고해주세요~

.map(TextReader::new)
.flatMap(reader -> reader.get().stream())
.toList();
}

private static String getContext(List<Document> documents) {
return documents.stream()
.map(Document::getText)
.collect(Collectors.joining("\n\n"));
}

private SearchRequest getSearchRequest(String query, int k) {
return SearchRequest.builder()
.query(query)
.topK(k)
.build();
}
}
48 changes: 48 additions & 0 deletions src/main/java/com/cholog/bootcamp/service/MarkdownReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.cholog.bootcamp.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.ai.document.Document;
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class MarkdownReader {

private final Resource[] resources;

public MarkdownReader(@Value("classpath:data/**/*.md") Resource[] resources) {
this.resources = resources;
}

public List<Document> loadAll() {
List<Document> allDocuments = new ArrayList<>();
for (Resource resource : resources) {
MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
.withHorizontalRuleCreateDocument(true)
.withIncludeCodeBlock(false)
.withIncludeBlockquote(false)
.withAdditionalMetadata("filename", resource.getFilename())
.build();

MarkdownDocumentReader reader = new MarkdownDocumentReader(resource, config);
allDocuments.addAll(reader.get());
}

allDocuments.forEach(doc -> log.info(
"filename={}, title={}, text={}",
doc.getMetadata().get("filename"),
doc.getMetadata().get("title"),
doc.getText()
));

return allDocuments;
}
}
2 changes: 2 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
spring:
config:
import: optional:file:.env[.properties] # 루트의 .env 파일이 존재하면 .properties 형식으로 읽어 임포트
application:
name: spring-ai-bootcamp-basic
ai:
Expand Down
Loading