Skip to content

[Feat] answer + citations 응답 조합 - #77

Merged
kangcheolung merged 14 commits into
developfrom
feature/75
Jul 29, 2026
Merged

[Feat] answer + citations 응답 조합#77
kangcheolung merged 14 commits into
developfrom
feature/75

Conversation

@kangcheolung

@kangcheolung kangcheolung commented Jul 29, 2026

Copy link
Copy Markdown
Member

🔍️ 작업 내용

✨ 상세 설명

RAG 블록(F-RAG-0105) 마지막 이슈입니다. Issue 14에서 만든 PromptBuilder/OllamaClient/RagResponseCommandService/ResponseCitationCommandService를 RagFacade로 묶어 기존 POST /search에 연결하고, 최종 응답에 answer/citations를 포함시킵니다.

  • RagFacade 신설 — SearchFacade와 별도 트랜잭션으로 분리 (LLM HTTP 호출이 검색 DB 작업과 같은 커넥션을 오래 물고 있지 않도록)
  • SearchController에서 searchFacade.search() 커밋 후 ragFacade.generate() 순차 호출, SearchResponse.withAnswer()로 병합
  • NO_CONTEXT(검색 결과 0건) 처리: RagResponseCommandService.createNoContext() — status=SUCCESS, 고정 문구, LLM 호출 생략, citations 빈 배열
  • SearchResponseanswer/citations 필드 추가 (기존 results 필드는 하위 호환을 위해 유지)
  • SearchResultCommandService.saveAll()List<SearchResult> 반환으로 변경하고, ResponseCitationCommandService.saveAll()에 전달해 response_citations.search_result_id를 채움 (Issue 4에서 미룬 부분)
  • SearchOutcome(search 도메인), RagAnswer(rag 도메인) — 각 Facade가 Controller에게 넘기는 내부 전달용 DTO
  • CitationResponse는 rag가 아닌 search 도메인에 배치 — RagFacade가 이미 search 도메인에 의존하고 있어 순환 참조를 피하기 위함

상세 설계 배경은 docs/design/kangcheolung-#75-rag-facade-integration.md 참고해주세요.

🛠️ 추후 리팩토링 및 고도화 계획

  • SearchFacade/RagFacade 둘 다 "트랜잭션 안에 HTTP 호출 포함" 트레이드오프가 남아있음 (MVP 단계 단순성 우선, 추후 분리 검토)
  • REQUIRES_NEW 트랜잭션 경계(markFailed/createFailed)는 아직 Spring 통합 테스트 없음 (Mockito 단위 테스트로만 검증)
  • 실제 문서 업로드/인덱싱 후 Swagger로 POST /search e2e 확인 예정

📸 스크린샷 (선택)

N/A

💬 리뷰 요구사항

  • SearchFacade/RagFacade 트랜잭션 분리 방식(Controller가 순차 호출)이 적절한지
  • CitationResponse를 search 도메인에 둔 순환 참조 회피 판단이 적절한지
  • NO_CONTEXT를 에러가 아닌 status=SUCCESS + 고정 문구로 처리한 설계가 적절한지

Summary by CodeRabbit

  • 새로운 기능

    • 검색 결과에 AI 생성 답변과 인용 출처가 함께 표시됩니다.
    • 인용 출처에 문서명, 페이지, 관련 본문 등 근거 정보가 제공됩니다.
    • 검색 결과가 없을 경우 안내 답변이 표시되며, 불필요한 AI 호출을 방지합니다.
  • 오류 처리

    • AI 답변 생성 실패 시 실패 상태를 기록하고 오류를 안정적으로 전달합니다.
  • 테스트

    • 검색, 답변 생성, 인용 연결 및 예외 상황에 대한 검증을 보강했습니다.

llmProvider/llmModelName이 각각 무엇을 저장하는 필드인지 명시한다.
void -> List<SearchResult>. 저장된 SearchResult의 id를 호출한 쪽이 받을 수
있게 해서, RagFacade가 response_citations.search_result_id를 채울 수 있게
한다 (Issue 4에서 예고한 변경).
…tions 필드 추가

SearchOutcome: SearchFacade가 SearchController에게 candidates/savedResults를
함께 넘기기 위한 내부 전달용 객체 (API로 노출되지 않음).
CitationResponse: 최종 응답에 노출되는 출처 1건. RagFacade(rag 도메인)가
이미 search 도메인에 의존하고 있어, 순환 참조를 피하기 위해 이 타입을
rag가 아닌 search 도메인에 둔다.
SearchResponse는 answer/citations 필드와 병합용 withAnswer()를 추가하고,
기존 results 필드는 하위 호환을 위해 그대로 유지한다.
반환 타입을 SearchResponse -> SearchOutcome으로 변경. 검색 로직 자체는
변경 없고, candidates/savedResults를 함께 실어 반환하도록 두 반환 지점만
수정한다.
검색 결과가 0건이라 LLM 호출을 생략한 경우, citation 없이 고정 안내
문구로 status=SUCCESS 기록을 남긴다. createFailed()의 고정 문구 패턴과
대칭되게 구현한다.
saveAll()에 List<SearchResult> 파라미터를 추가해 Issue 4에서 비워뒀던
search_result_id를 채운다. candidates와 searchResults는
SearchResultCommandService.saveAll()이 동일한 순서로 만든 것이라는
전제로 인덱스 기반 1:1 매핑한다.
Issue 1~4에서 만든 PromptBuilder/OllamaClient/RagResponseCommandService/
ResponseCitationCommandService를 순서대로 호출하는 조율자. SearchFacade와
별도 트랜잭션으로 분리해, Ollama HTTP 호출이 검색 DB 작업과 같은
커넥션을 오래 물고 있지 않게 한다. candidates가 비어있으면(NO_CONTEXT)
LLM 호출을 생략하고, 실패 시 createFailed()로 기록 후 예외를 재전파해
GlobalExceptionHandler가 503으로 변환하게 한다.
searchFacade.search() 완료 후 ragFacade.generate()를 순차 호출하고,
SearchResponse.withAnswer()로 병합해 반환한다. RAG 블록은 자기만의
엔드포인트를 갖지 않고 기존 POST /search를 확장하는 구조다.
SearchFacadeTest 3개 테스트를 SearchOutcome 접근 경로(outcome.response())로
수정하고, SearchResultCommandServiceTest에 saveAll() 반환값 검증을 추가한다.
createNoContext 테스트 추가, ResponseCitationCommandServiceTest를 변경된
saveAll() 시그니처(searchResults 인자)에 맞춰 수정하고 search_result_id가
실제로 연결되는지 검증한다. RagFacadeTest는 NO_CONTEXT/정상/실패 3가지
흐름을 검증한다.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kangcheolung, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a4d62222-3351-474d-af31-2202ead32992

📥 Commits

Reviewing files that changed from the base of the PR and between bd5aabf and 2414f8f.

📒 Files selected for processing (5)
  • docs/design/kangcheolung-#75-rag-facade-integration.md
  • src/main/java/com/opensource/docgrid/domain/rag/entity/RagResponse.java
  • src/main/java/com/opensource/docgrid/domain/rag/service/command/ResponseCitationCommandService.java
  • src/test/java/com/opensource/docgrid/domain/rag/service/command/ResponseCitationCommandServiceTest.java
  • src/test/java/com/opensource/docgrid/domain/search/service/SearchFacadeTest.java
📝 Walkthrough

Walkthrough

검색 단계가 저장된 결과와 후보를 SearchOutcome으로 전달하도록 확장되고, RagFacade가 답변·인용을 생성·저장한 뒤 SearchController가 최종 SearchResponse로 조합한다. 검색 결과가 없으면 고정 답변을 반환하며, LLM 실패 시 실패 응답을 저장하고 예외를 재전파한다.

Changes

RAG 검색 응답 통합

Layer / File(s) Summary
검색 결과 전달 계약
src/main/java/com/opensource/docgrid/domain/search/dto/..., src/main/java/com/opensource/docgrid/domain/search/service/..., src/test/java/com/opensource/docgrid/domain/search/...
SearchOutcome이 검색 응답, 벡터 후보, 저장된 검색 결과를 전달하고, SearchResponseanswercitations를 포함하도록 변경되었다.
RAG 생성과 인용 저장
src/main/java/com/opensource/docgrid/domain/rag/..., src/test/java/com/opensource/docgrid/domain/rag/...
RagFacade가 NO_CONTEXT, 정상 생성, 실패 흐름을 조율하며, 인용을 저장된 SearchResult와 연결하도록 변경되었다.
검색 엔드포인트 조합
src/main/java/com/opensource/docgrid/domain/search/controller/SearchController.java, docs/design/...
SearchController가 검색 후 RagFacade.generate()를 호출하고, 반환된 답변과 인용을 최종 응답에 병합하는 흐름과 검증 계획을 문서화했다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SearchController
  participant SearchFacade
  participant SearchResultRepository
  participant RagFacade
  participant OllamaClient
  participant ResponseCitationCommandService
  SearchController->>SearchFacade: search(userId, request)
  SearchFacade->>SearchResultRepository: saveAll(search results)
  SearchFacade-->>SearchController: SearchOutcome
  SearchController->>RagFacade: generate(queryId, queryText, candidates, savedResults)
  RagFacade->>OllamaClient: generate(prompt)
  RagFacade->>ResponseCitationCommandService: saveAll(response, candidates, savedResults)
  RagFacade-->>SearchController: RagAnswer
  SearchController-->>SearchController: withAnswer(answer, citations)
Loading

Possibly related PRs

  • DocGrid/backend#57: 동일한 /search 흐름과 SearchController/SearchFacade 조합을 확장한다.
  • DocGrid/backend#72: RagResponseCommandService의 성공·실패 저장 흐름을 직접 확장한다.
  • DocGrid/backend#74: ResponseCitationCommandService.saveAll의 인용 저장 경로를 확장한다.

Suggested labels: ✨ Feature

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 answer와 citations를 POST /search 응답에 결합한다는 핵심 변경을 짧고 명확하게 요약합니다.
Description check ✅ Passed 템플릿의 작업 내용, 상세 설명, 추후 계획, 스크린샷, 리뷰 요구사항을 모두 채워 전체적으로 완성도 있습니다.
Linked Issues check ✅ Passed RagFacade 분리, 순차 호출, saved results 반환, NO_CONTEXT 처리, SearchResponse 확장 등 #75 요구사항을 충족합니다.
Out of Scope Changes check ✅ Passed 설계 문서, 테스트, 주석 수준 변경만 보이며 #75 범위를 벗어나는 변경은 보이지 않습니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/75

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/java/com/opensource/docgrid/domain/search/service/command/SearchResultCommandServiceTest.java (1)

55-65: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

repository의 실제 반환값 전파를 검증하도록 stub을 분리하세요.

현재 saveAll stub은 입력 리스트를 그대로 반환하므로, 서비스가 repository 결과 대신 입력 리스트를 반환해도 테스트가 통과합니다. 입력 리스트와 다른 persisted 리스트를 반환하도록 설정하고 동일 인스턴스인지 검증해야 변경된 계약을 제대로 보호할 수 있습니다.

+        List<SearchResult> persisted = List.of(mock(SearchResult.class), mock(SearchResult.class));
-        given(searchResultRepository.saveAll(any())).willAnswer(i -> i.getArgument(0));
+        given(searchResultRepository.saveAll(any())).willReturn(persisted);

         List<SearchResult> returned = searchResultCommandService.saveAll(query, List.of(c1, c2));

+        assertThat(returned).isSameAs(persisted);

테스트 경로 지침의 “테스트 커버리지 ... 확인” 요구에 따른 수정입니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/com/opensource/docgrid/domain/search/service/command/SearchResultCommandServiceTest.java`
around lines 55 - 65, SearchResultCommandServiceTest의 saveAll 테스트에서
searchResultRepository.saveAll stub이 입력 리스트와 다른 persisted 리스트를 반환하도록 분리하세요. 서비스
호출 후 returned가 persisted와 동일한 인스턴스인지 검증하고, 입력값 검증은 기존 captor 검증으로 유지해 repository
실제 반환값 전파 계약을 보호하세요.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/opensource/docgrid/domain/rag/entity/RagResponse.java`:
- Around line 60-64: RagResponse의 llmProvider 및 인접한 모델 필드 설명 주석에서 “Ollama”와
“qwen2.5:3b” 같은 특정 값을 제거하고, 각각 LLM 제공자 이름과 LLM 모델 이름이라는 역할만 설명하도록 수정하세요.

In `@src/main/java/com/opensource/docgrid/domain/rag/service/RagFacade.java`:
- Line 23: RagFacade의 클래스 Javadoc에서 내부 순번 라벨 “F-RAG-05”를 제거하고, 해당 위치에는 실제 이슈 번호나
RAG 답변 생성 흐름을 조율한다는 설명형 문구만 남기세요.

In
`@src/main/java/com/opensource/docgrid/domain/search/dto/response/CitationResponse.java`:
- Around line 7-14: CitationResponse record에 클래스 수준 주석을 추가해 역할과 책임, API 응답 경계를
설명하세요. 기존 필드와 Schema 설명은 변경하지 말고, record 선언 바로 앞에 프로젝트의 Javadoc 스타일로 작성하세요.

In
`@src/main/java/com/opensource/docgrid/domain/search/service/SearchFacade.java`:
- Around line 107-113: Update the SearchFacade flow and ResponseCitation mapping
so RAG citations retain each saved SearchResult reference after the transaction.
Pass searchResultId values from savedResults, resolve them with
EntityManager.getReference(SearchResult.class, ...) like the existing chunkId
mapping, and support nullable searchResult fields if required. Add unit and
integration coverage verifying search_result_id is persisted.

In
`@src/test/java/com/opensource/docgrid/domain/search/service/SearchFacadeTest.java`:
- Around line 76-82: Update the SearchFacadeTest success-path setup and
assertions around searchFacade.search so searchResultCommandService.saveAll
returns a concrete saved result, then assert SearchOutcome.savedResults()
contains and preserves that exact value. Keep the existing candidate and
response assertions, ensuring the test fails if saved results are omitted or
altered.

---

Outside diff comments:
In
`@src/test/java/com/opensource/docgrid/domain/search/service/command/SearchResultCommandServiceTest.java`:
- Around line 55-65: SearchResultCommandServiceTest의 saveAll 테스트에서
searchResultRepository.saveAll stub이 입력 리스트와 다른 persisted 리스트를 반환하도록 분리하세요. 서비스
호출 후 returned가 persisted와 동일한 인스턴스인지 검증하고, 입력값 검증은 기존 captor 검증으로 유지해 repository
실제 반환값 전파 계약을 보호하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 034b9ce6-ac09-48ee-9ef9-2fa536304a4b

📥 Commits

Reviewing files that changed from the base of the PR and between 50da23e and bd5aabf.

📒 Files selected for processing (17)
  • docs/design/kangcheolung-#75-rag-facade-integration.md
  • src/main/java/com/opensource/docgrid/domain/rag/dto/RagAnswer.java
  • src/main/java/com/opensource/docgrid/domain/rag/entity/RagResponse.java
  • src/main/java/com/opensource/docgrid/domain/rag/service/RagFacade.java
  • src/main/java/com/opensource/docgrid/domain/rag/service/command/RagResponseCommandService.java
  • src/main/java/com/opensource/docgrid/domain/rag/service/command/ResponseCitationCommandService.java
  • src/main/java/com/opensource/docgrid/domain/search/controller/SearchController.java
  • src/main/java/com/opensource/docgrid/domain/search/dto/SearchOutcome.java
  • src/main/java/com/opensource/docgrid/domain/search/dto/response/CitationResponse.java
  • src/main/java/com/opensource/docgrid/domain/search/dto/response/SearchResponse.java
  • src/main/java/com/opensource/docgrid/domain/search/service/SearchFacade.java
  • src/main/java/com/opensource/docgrid/domain/search/service/command/SearchResultCommandService.java
  • src/test/java/com/opensource/docgrid/domain/rag/service/RagFacadeTest.java
  • src/test/java/com/opensource/docgrid/domain/rag/service/command/RagResponseCommandServiceTest.java
  • src/test/java/com/opensource/docgrid/domain/rag/service/command/ResponseCitationCommandServiceTest.java
  • src/test/java/com/opensource/docgrid/domain/search/service/SearchFacadeTest.java
  • src/test/java/com/opensource/docgrid/domain/search/service/command/SearchResultCommandServiceTest.java

Comment thread src/main/java/com/opensource/docgrid/domain/rag/entity/RagResponse.java Outdated
import lombok.extern.slf4j.Slf4j;

/**
* RAG 답변 생성 전체 흐름을 조율하는 Facade (F-RAG-05).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

내부 번호 라벨을 설명형 문구로 바꾸세요.

F-RAG-05 형식의 내부 순번 라벨이 코드 Javadoc에 노출됩니다. 실제 이슈 번호 또는 기능 설명만 남기세요.

수정 예시
- * RAG 답변 생성 전체 흐름을 조율하는 Facade (F-RAG-05).
+ * RAG 답변 생성·저장 전체 흐름을 조율하는 Facade.

As per coding guidelines, “Never expose private numbered PR sequence labels in … code, or comments.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* RAG 답변 생성 전체 흐름을 조율하는 Facade (F-RAG-05).
* RAG 답변 생성·저장 전체 흐름을 조율하는 Facade.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/opensource/docgrid/domain/rag/service/RagFacade.java` at
line 23, RagFacade의 클래스 Javadoc에서 내부 순번 라벨 “F-RAG-05”를 제거하고, 해당 위치에는 실제 이슈 번호나
RAG 답변 생성 흐름을 조율한다는 설명형 문구만 남기세요.

Source: Coding guidelines

Comment on lines +7 to +14
public record CitationResponse(
@Schema(description = "인용 라벨") String label,
@Schema(description = "출처 문서 ID") Long documentId,
@Schema(description = "출처 문서 제목") String documentTitle,
@Schema(description = "근거 chunk ID") Long chunkId,
@Schema(description = "원본 문서 페이지 번호, 페이지 개념이 없는 형식은 null") Integer pageNo,
@Schema(description = "인용된 텍스트") String quotedText
) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

신규 DTO에 클래스 수준 주석을 추가해 주세요.

CitationResponse는 새로 생성된 record지만 역할, 책임, API 경계를 설명하는 class-level comment가 없습니다.

제안
+/**
+ * RAG 답변에 포함되는 검색 출처 1건을 표현하는 읽기 전용 응답 DTO.
+ *
+ * <p>검색 후보의 문서·청크 메타데이터를 API 응답 형태로 변환한다.
+ */
 public record CitationResponse(

코딩 가이드라인의 “새로 생성된 class, interface, record에는 역할·책임·경계를 설명하는 class-level comment가 있어야 한다” 규칙에 따른 수정입니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public record CitationResponse(
@Schema(description = "인용 라벨") String label,
@Schema(description = "출처 문서 ID") Long documentId,
@Schema(description = "출처 문서 제목") String documentTitle,
@Schema(description = "근거 chunk ID") Long chunkId,
@Schema(description = "원본 문서 페이지 번호, 페이지 개념이 없는 형식은 null") Integer pageNo,
@Schema(description = "인용된 텍스트") String quotedText
) {
/**
* RAG 답변에 포함되는 검색 출처 1건을 표현하는 읽기 전용 응답 DTO.
*
* <p>검색 후보의 문서·청크 메타데이터를 API 응답 형태로 변환한다.
*/
public record CitationResponse(
`@Schema`(description = "인용 라벨") String label,
`@Schema`(description = "출처 문서 ID") Long documentId,
`@Schema`(description = "출처 문서 제목") String documentTitle,
`@Schema`(description = "근거 chunk ID") Long chunkId,
`@Schema`(description = "원본 문서 페이지 번호, 페이지 개념이 없는 형식은 null") Integer pageNo,
`@Schema`(description = "인용된 텍스트") String quotedText
) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/opensource/docgrid/domain/search/dto/response/CitationResponse.java`
around lines 7 - 14, CitationResponse record에 클래스 수준 주석을 추가해 역할과 책임, API 응답 경계를
설명하세요. 기존 필드와 Schema 설명은 변경하지 말고, record 선언 바로 앞에 프로젝트의 Javadoc 스타일로 작성하세요.

Source: Coding guidelines

Comment on lines +107 to +113
List<SearchResult> savedResults = searchResultCommandService.saveAll(searchQuery, verified);

int latency = latencyMs(start);
searchQueryCommandService.markSuccess(searchQuery, latency);
log.info("[SEARCH] done queryId={} results={} latencyMs={}", searchQuery.getId(), verified.size(), latency);

return SearchResponse.of(searchQuery.getId(), verified);
return new SearchOutcome(SearchResponse.of(searchQuery.getId(), verified), verified, savedResults);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

rg -n -C 6 'class ResponseCitation|searchResult|`@ManyToOne`|cascade' src/main/java

Repository: DocGrid/backend

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files | rg 'SearchOutcome|SearchQuery\.java|SearchResultCommandService\.java|ResponseCitationCommandService\.java|RagFacade\.java|ResponseCitation\.java|SearchResult\.java|SearchQueryRepository\.java|SearchResultRepository\.java|ResponseCitationRepository\.java' || true

echo
echo "== SearchOutcome =="
rg -n -C 8 'record SearchOutcome|class SearchOutcome|SearchOutcome' src/main/java || true

echo
echo "== SearchQueryCommandService =="
fd -a 'SearchQueryCommandService\.java$' . | while read -r f; do
  echo "--- ${f#$(pwd)/} ---"
  wc -l "$f"
  sed -n '1,180p' "$f"
done

echo
echo "== ResponseCitationCommandService =="
fd -a 'ResponseCitationCommandService\.java$' . | while read -r f; do
  echo "--- ${f#$(pwd)/} ---"
  wc -l "$f"
  sed -n '1,80p' "$f"
done

echo
echo "== ResponseCitation entity/repository =="
fd -a 'ResponseCitation.*\.java$' . | while read -r f; do
  if echo "$f" | rg '(entity|repository|Repository)' >/dev/null; then
    echo "--- ${f#$(pwd)/} ---"
    wc -l "$f"
    sed -n '1,180p' "$f"
  fi
done

echo
echo "== search result repositories =="
fd -a 'SearchQueryRepository\.java$|SearchResultRepository\.java$' . | while read -r f; do
  echo "--- ${f#$(pwd)/} ---"
  wc -l "$f"
  sed -n '1,220p' "$f"
done

echo
echo "== SearchQuery entity relevant mapping =="
fd -a 'SearchQuery\.java$' . | while read -r f; do
  echo "--- ${f#$(pwd)/} ---"
  sed -n '1,180p' "$f"
done

Repository: DocGrid/backend

Length of output: 31127


🏁 Script executed:

rg -n -C 6 'class ResponseCitation|searchResult|`@ManyToOne`|cascade' src/main/java

Repository: DocGrid/backend

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ResponseCitation searchResult builder/setter excerpts =="
rg -n -C 4 'searchResult|search_result_id' src/main/java/com/opensource/docgrid/domain/rag/entity src/main/java/com/opensource/docgrid/domain/rag/service

echo
echo "== ResponseCitation full builder annotation block =="
sed -n '90,180p' src/main/java/com/opensource/docgrid/domain/rag/entity/ResponseCitation.java

echo
echo "== Search facade transaction outline =="
sed -n '1,28p' src/main/java/com/opensource/docgrid/domain/search/service/SearchFacade.java
sed -n '35,120p' src/main/java/com/opensource/docgrid/domain/search/service/SearchFacade.java

echo
echo "== RAG facade relevant transaction outline =="
sed -n '1,75p' src/main/java/com/opensource/docgrid/domain/rag/service/RagFacade.java

echo
echo "== all `@Transactional` declarations around Search/RAG controllers/services =="
rg -n -C 3 '`@Transactional`' src/main/java/com/opensource/docgrid/domain/search src/main/java/com/opensource/docgrid/domain/rag

Repository: DocGrid/backend

Length of output: 29961


RAG 트랜잭션에서 SearchResult를 참조할 수 있게 해 주세요.

savedResults는 검색 트랜잭션 종료가후 detached 상태라서 ResponseCitation.searchResult(build)로 직접 연결하면 search_result_id가 저장에서 누락됩니다. 기존 chunkId처럼 searchResultId만 넘겨 entityManager.getReference(SearchResult.class, ...)로 연결하거나, searchResult 필드를 nullable로 두고 ID 기반 매핑을 적용해 주세요. 함께 save_result_id 저장을 단가/통합 테스트로 검증해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/opensource/docgrid/domain/search/service/SearchFacade.java`
around lines 107 - 113, Update the SearchFacade flow and ResponseCitation
mapping so RAG citations retain each saved SearchResult reference after the
transaction. Pass searchResultId values from savedResults, resolve them with
EntityManager.getReference(SearchResult.class, ...) like the existing chunkId
mapping, and support nullable searchResult fields if required. Add unit and
integration coverage verifying search_result_id is persisted.

Comment thread src/test/java/com/opensource/docgrid/domain/search/service/SearchFacadeTest.java Outdated
ResponseCitationCommandService: SearchFacade 트랜잭션에서 넘어온(detached)
SearchResult 엔티티를 그대로 FK에 대입하던 것을, chunk 필드와 동일하게
entityManager.getReference(SearchResult.class, id)로 통일한다.
RagResponse: llmModelName은 하드코딩이 아니라 OllamaGenerateResult.model()에서
동적으로 채워지는 값이라, 필드 주석에서 특정 모델명 예시를 제거한다.
ResponseCitationCommandServiceTest: getReference(SearchResult.class, id)
방식으로 바뀐 구현에 맞춰 mocking을 수정한다.
SearchFacadeTest: saveAll()이 빈 리스트를 반환하도록 stub되어 있어
SearchOutcome.savedResults() 전달 여부를 검증하지 못하던 것을 보강한다.
CodeRabbit 코멘트 5건의 처리 내역과, entityManager.getReference()가
성능 목적/트랜잭션 안전성 목적 두 가지로 쓰이는 이유를 정리한다.
@kangcheolung
kangcheolung merged commit fdbfc69 into develop Jul 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] answer + citations 응답 조합

1 participant