Skip to content

[Feat] 질문 임베딩 + search_queries 저장 - #45

Merged
kangcheolung merged 9 commits into
developfrom
feature/44
Jul 22, 2026
Merged

[Feat] 질문 임베딩 + search_queries 저장#45
kangcheolung merged 9 commits into
developfrom
feature/44

Conversation

@kangcheolung

@kangcheolung kangcheolung commented Jul 22, 2026

Copy link
Copy Markdown
Member

🔍️작업 내용

✨ 상세 설명

검색 블록의 첫 번째 구현 이슈. POST /search API 조립 전 필요한 서비스 레이어 부품을 구현했습니다.

질문 임베딩 (QueryEmbeddingService)

사용자 검색어를 Python 사이드카 서버(POST /embed)에 전달해 1024차원 벡터로 변환합니다.

  • active 임베딩 모델 조회 (EmbeddingModelQueryService 재사용)
  • RestClient 기반 HTTP 호출 (JDK HttpClient, timeout 5s)
  • 예외 처리: active 모델 없음(500) / 차원 불일치(500) / 서버 장애(503)
  • 반환: EmbedResult(model, vector) — Issue 5 조립 시 재사용

F-SEARCH-03 — 검색 요청 로깅 (SearchQueryCommandService)

검색 요청을 search_queries 테이블에 상태 흐름으로 기록합니다.

  • createProcessing() — PROCESSING 상태로 저장 (query_id 발급)
  • markSuccess(latencyMs) — SUCCESS + latency_ms 갱신
  • markFailed(errorMessage) — FAILED + error_message 갱신
  • dirty checking 활용, 갱신 시 명시적 save 없음

기타 변경사항

파일 변경 내용
ResultStatus PROCESSING 추가
ErrorCode EMBEDDING_SERVER_UNAVAILABLE, EMBEDDING_DIMENSION_MISMATCH 추가
application.yml embedding.server.base-url 설정 추가
SearchQuery updateToSuccess(), updateToFailed() 메서드 추가

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

  • Issue 5에서 QueryEmbeddingService + SearchQueryCommandService를 조합해 POST /search API 조립 예정
  • search_type = VECTOR 고정 → Issue 명세상 HYBRID는 2단계 확장 예정
  • latency_ms 포트폴리오 성능 증빙 지표로 활용 예정 (통합 테스트 단계에서 측정)

📸 스크린샷 (선택)

💬 리뷰 요구사항

  • QueryEmbeddingServiceRestClientException 단일 catch 처리가 적절한지 (타임아웃 / 4xx / 5xx를
    동일하게 503으로 처리)
  • SearchQueryCommandService.markSuccess/markFailed의 dirty checking 방식이 트랜잭션 경계 내에서 올바르게
    동작하는지

Summary by CodeRabbit

  • 새 기능

    • 검색어를 임베딩 서버로 변환하고 검색 처리에 활용할 수 있습니다.
    • 검색 요청 상태가 처리 중, 성공, 실패로 관리됩니다.
    • 성공 시 처리 시간, 실패 시 오류 메시지가 기록됩니다.
  • 오류 처리

    • 임베딩 서버 연결 장애와 임베딩 차원 불일치 오류를 구분해 안내합니다.
  • 테스트

    • 임베딩 생성 및 검색 상태 전환에 대한 검증이 추가되었습니다.

kangcheolung and others added 8 commits July 22, 2026 14:29
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ut 5s)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 22, 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: 48 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: 093854b7-8b73-40fc-8b92-89b0db405318

📥 Commits

Reviewing files that changed from the base of the PR and between 3997de7 and 11feed3.

📒 Files selected for processing (2)
  • src/main/java/com/opensource/docgrid/domain/embedding/service/query/QueryEmbeddingService.java
  • src/test/java/com/opensource/docgrid/domain/search/service/command/SearchQueryCommandServiceTest.java
📝 Walkthrough

Walkthrough

질의 임베딩 DTO와 외부 서버 연동 서비스를 추가하고, 임베딩 서버 장애 및 차원 불일치 오류를 처리한다. 검색 쿼리는 PROCESSING으로 저장한 뒤 SUCCESS 또는 FAILED로 갱신되며, 관련 단위 테스트와 설계 문서가 추가되었다.

Changes

검색 임베딩 및 쿼리 로깅

Layer / File(s) Summary
임베딩 요청 및 응답 검증
src/main/java/com/opensource/docgrid/domain/embedding/..., src/main/java/com/opensource/docgrid/global/config/EmbeddingServerConfig.java, src/main/resources/application.yml, src/main/java/com/opensource/docgrid/global/exception/ErrorCode.java, src/test/java/com/opensource/docgrid/domain/embedding/...
임베딩 요청·응답 DTO, 전용 RestClient, 활성 모델 조회와 /embed 호출, 장애 및 차원 불일치 예외 처리와 테스트를 추가한다.
검색 쿼리 상태 저장 및 전이
src/main/java/com/opensource/docgrid/domain/search/..., src/test/java/com/opensource/docgrid/domain/search/...
검색 쿼리를 VECTORPROCESSING 상태로 저장하고, dirty checking으로 성공·실패 상태와 latency 또는 오류 메시지를 갱신한다.

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

Possibly related issues

  • DocGrid/backend#43 — 임베딩 생성과 search_queries 상태 로깅 흐름을 함께 구현한다.

Possibly related PRs

  • DocGrid/backend#11 — 활성 임베딩 모델 조회에 사용되는 EmbeddingModelQueryService와 직접 연결된다.
  • DocGrid/backend#42SearchQueryfloat[] 벡터 필드와 직접 맞물린다.
  • DocGrid/backend#6 — 기존 검색 도메인 타입 위에 처리 상태와 상태 전이 로직을 확장한다.

Suggested labels: ✨ Feature

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #44의 임베딩/저장 흐름은 구현됐지만, 빈 문자열 질문을 400으로 처리하는 검증 요구는 확인되지 않습니다. QueryEmbeddingService 또는 요청 검증 계층에 빈 질문에 대한 400 VALIDATION_ERROR 처리와 테스트를 추가하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 질문 임베딩과 search_queries 저장이라는 핵심 변경을 간결하게 잘 요약합니다.
Description check ✅ Passed 필수 섹션인 작업 내용·상세 설명·추후 계획·리뷰 요구사항을 채웠고, 스크린샷은 선택이라 누락이 문제되지 않습니다.
Out of Scope Changes check ✅ Passed 변경은 임베딩/검색 로깅 범위에 맞고, 별도 기능이나 무관한 리팩토링은 보이지 않습니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/44

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

🤖 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 `@docs/design/kangcheolung-`#44-search-embedding-query-logging.md:
- Line 102: 문서의 언어 식별자가 없는 네 개의 코드 펜스를 찾아 각각 ```text 또는 내용에 맞는 언어 태그를 추가하세요. 기존
코드 블록 내용과 문서 구조는 유지하고 markdownlint MD040 경고가 발생하지 않도록 수정하세요.

In
`@src/main/java/com/opensource/docgrid/domain/embedding/service/query/QueryEmbeddingService.java`:
- Around line 47-53: Update the validation before constructing EmbedResult in
QueryEmbeddingService to also reject null vectors and vectors whose length
differs from activeModel.getDimension(), alongside the existing response and
metadata-dimension checks. Ensure all mismatches log the actual vector dimension
when available and throw EMBEDDING_DIMENSION_MISMATCH; add coverage for a null
vector and a metadata/vector length mismatch.
- Around line 32-41: Update QueryEmbeddingService.embed to validate text at
method entry and reject null, empty, or whitespace-only input with the existing
400 validation mechanism before calling getActiveModel or the /embed endpoint.
Add a test covering blank input and confirming no external embedding request is
made.

In `@src/main/java/com/opensource/docgrid/domain/search/entity/SearchQuery.java`:
- Around line 105-113: Restrict the state transitions in
SearchQuery.updateToSuccess and SearchQuery.updateToFailed so they apply only
when the current status is PROCESSING; otherwise leave the status and associated
latencyMs/errorMessage unchanged. Add boundary tests covering repeated and
conflicting calls, including SUCCESS → FAILED and FAILED → SUCCESS.

In
`@src/test/java/com/opensource/docgrid/domain/search/service/command/SearchQueryCommandServiceTest.java`:
- Around line 36-46: Update the createProcessing test around
searchQueryRepository.save and SearchQueryCommandService.createProcessing to
capture the actual save argument with ArgumentCaptor<SearchQuery> instead of
asserting only the stubbed return value. Verify the captured SearchQuery has
PROCESSING status, the expected searchType, model, vector, and topK, and add the
required ArgumentCaptor and SearchType imports.
🪄 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: d80d0acb-9787-4bfe-93ac-3b9eb5b7800e

📥 Commits

Reviewing files that changed from the base of the PR and between 86b1ecc and 3997de7.

📒 Files selected for processing (15)
  • docs/design/kangcheolung-#44-search-embedding-query-logging.md
  • src/main/java/com/opensource/docgrid/domain/embedding/dto/EmbedResult.java
  • src/main/java/com/opensource/docgrid/domain/embedding/dto/request/EmbedRequest.java
  • src/main/java/com/opensource/docgrid/domain/embedding/dto/response/EmbedServerResponse.java
  • src/main/java/com/opensource/docgrid/domain/embedding/service/query/QueryEmbeddingService.java
  • src/main/java/com/opensource/docgrid/domain/search/entity/SearchQuery.java
  • src/main/java/com/opensource/docgrid/domain/search/enums/ResultStatus.java
  • src/main/java/com/opensource/docgrid/domain/search/repository/SearchQueryRepository.java
  • src/main/java/com/opensource/docgrid/domain/search/service/command/SearchQueryCommandService.java
  • src/main/java/com/opensource/docgrid/global/config/EmbeddingServerConfig.java
  • src/main/java/com/opensource/docgrid/global/exception/ErrorCode.java
  • src/main/resources/application.yml
  • src/test/java/com/opensource/docgrid/domain/embedding/service/query/QueryEmbeddingServiceTest.java
  • src/test/java/com/opensource/docgrid/domain/search/fixture/SearchQueryFixture.java
  • src/test/java/com/opensource/docgrid/domain/search/service/command/SearchQueryCommandServiceTest.java


**`embed(String text)` 처리 흐름:**

```

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

코드 펜스에 언어 식별자를 추가하세요.

markdownlint-cli2의 MD040 경고가 발생한 네 블록에 text 또는 적절한 언어 태그를 지정해야 합니다.

수정 예시
-```
+```text

Also applies to: 127-127, 136-136, 144-144

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 102-102: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 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 `@docs/design/kangcheolung-`#44-search-embedding-query-logging.md at line 102,
문서의 언어 식별자가 없는 네 개의 코드 펜스를 찾아 각각 ```text 또는 내용에 맞는 언어 태그를 추가하세요. 기존 코드 블록 내용과 문서
구조는 유지하고 markdownlint MD040 경고가 발생하지 않도록 수정하세요.

Source: Linters/SAST tools

Comment on lines +32 to +41
public EmbedResult embed(String text) {
EmbeddingModel activeModel = embeddingModelQueryService.getActiveModel();

EmbedServerResponse response;
try {
response = restClient.post()
.uri("/embed")
.body(new EmbedRequest(text))
.retrieve()
.body(EmbedServerResponse.class);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

빈 문자열 요청을 외부 서버 호출 전에 400으로 거부하세요.

현재 embed("") 또는 공백 문자열도 활성 모델 조회 후 /embed로 전송됩니다. 요구사항의 400 validation 계약을 충족하도록 서비스 진입 시점에 검증하고, 빈 입력 테스트도 추가해야 합니다.

수정 예시
 public EmbedResult embed(String text) {
+    if (text == null || text.isBlank()) {
+        throw new DocGridException(ErrorCode.INVALID_PARAMETER);
+    }
+
     EmbeddingModel activeModel = embeddingModelQueryService.getActiveModel();
📝 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 EmbedResult embed(String text) {
EmbeddingModel activeModel = embeddingModelQueryService.getActiveModel();
EmbedServerResponse response;
try {
response = restClient.post()
.uri("/embed")
.body(new EmbedRequest(text))
.retrieve()
.body(EmbedServerResponse.class);
public EmbedResult embed(String text) {
if (text == null || text.isBlank()) {
throw new DocGridException(ErrorCode.INVALID_PARAMETER);
}
EmbeddingModel activeModel = embeddingModelQueryService.getActiveModel();
EmbedServerResponse response;
try {
response = restClient.post()
.uri("/embed")
.body(new EmbedRequest(text))
.retrieve()
.body(EmbedServerResponse.class);
🤖 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/embedding/service/query/QueryEmbeddingService.java`
around lines 32 - 41, Update QueryEmbeddingService.embed to validate text at
method entry and reject null, empty, or whitespace-only input with the existing
400 validation mechanism before calling getActiveModel or the /embed endpoint.
Add a test covering blank input and confirming no external embedding request is
made.

Comment on lines +105 to +113
public void updateToSuccess(int latencyMs) {
this.status = ResultStatus.SUCCESS;
this.latencyMs = latencyMs;
}

public void updateToFailed(String errorMessage) {
this.status = ResultStatus.FAILED;
this.errorMessage = errorMessage;
}

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

SearchQuery 상태 전이를 제한하세요.

현재 메서드는 PROCESSING 여부를 확인하지 않아 SUCCESS → FAILED, FAILED → SUCCESS 같은 재전이를 허용합니다. 재시도나 중복 호출이 발생하면 상태와 latencyMs/errorMessage가 서로 다른 실행의 값으로 저장될 수 있습니다.

PROCESSING에서만 전이하도록 방어하거나, 중복 호출을 허용한다면 필드 덮어쓰기 규칙을 명시하고 해당 경계 테스트를 추가하세요.

🤖 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/entity/SearchQuery.java`
around lines 105 - 113, Restrict the state transitions in
SearchQuery.updateToSuccess and SearchQuery.updateToFailed so they apply only
when the current status is PROCESSING; otherwise leave the status and associated
latencyMs/errorMessage unchanged. Add boundary tests covering repeated and
conflicting calls, including SUCCESS → FAILED and FAILED → SUCCESS.

Comment on lines +36 to +46
EmbeddingModel model = EmbeddingModelFixture.createDefaultModel();
SearchQuery saved = SearchQueryFixture.createProcessing();
given(searchQueryRepository.save(any(SearchQuery.class))).willReturn(saved);

SearchQuery result = searchQueryCommandService.createProcessing(
null, null, SearchQueryFixture.QUERY_TEXT,
model, SearchQueryFixture.VECTOR, SearchQueryFixture.TOP_K
);

assertThat(result.getStatus()).isEqualTo(ResultStatus.PROCESSING);
then(searchQueryRepository).should(times(1)).save(any(SearchQuery.class));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

mock 반환값이 아니라 실제 저장 인자를 검증하세요.

현재 테스트는 SearchQueryFixture.createProcessing()을 mock 반환값으로 사용하므로, 서비스가 PROCESSING이 아닌 객체를 저장해도 테스트가 통과할 수 있습니다. ArgumentCaptor<SearchQuery>save()에 전달된 객체를 캡처해 상태, searchType, 모델, 벡터, topK를 검증하세요.

수정 예시
-        SearchQuery saved = SearchQueryFixture.createProcessing();
-        given(searchQueryRepository.save(any(SearchQuery.class))).willReturn(saved);
+        given(searchQueryRepository.save(any(SearchQuery.class)))
+            .willAnswer(invocation -> invocation.getArgument(0));

...
+        ArgumentCaptor<SearchQuery> captor = ArgumentCaptor.forClass(SearchQuery.class);
+        then(searchQueryRepository).should(times(1)).save(captor.capture());
+        SearchQuery persisted = captor.getValue();
+        assertThat(persisted.getStatus()).isEqualTo(ResultStatus.PROCESSING);
+        assertThat(persisted.getSearchType()).isEqualTo(SearchType.VECTOR);
+        assertThat(persisted.getQueryEmbeddingModel()).isSameAs(model);
+        assertThat(persisted.getTopK()).isEqualTo(SearchQueryFixture.TOP_K);

필요한 ArgumentCaptorSearchType import도 함께 추가하세요.

📝 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
EmbeddingModel model = EmbeddingModelFixture.createDefaultModel();
SearchQuery saved = SearchQueryFixture.createProcessing();
given(searchQueryRepository.save(any(SearchQuery.class))).willReturn(saved);
SearchQuery result = searchQueryCommandService.createProcessing(
null, null, SearchQueryFixture.QUERY_TEXT,
model, SearchQueryFixture.VECTOR, SearchQueryFixture.TOP_K
);
assertThat(result.getStatus()).isEqualTo(ResultStatus.PROCESSING);
then(searchQueryRepository).should(times(1)).save(any(SearchQuery.class));
EmbeddingModel model = EmbeddingModelFixture.createDefaultModel();
given(searchQueryRepository.save(any(SearchQuery.class)))
.willAnswer(invocation -> invocation.getArgument(0));
SearchQuery result = searchQueryCommandService.createProcessing(
null, null, SearchQueryFixture.QUERY_TEXT,
model, SearchQueryFixture.VECTOR, SearchQueryFixture.TOP_K
);
ArgumentCaptor<SearchQuery> captor = ArgumentCaptor.forClass(SearchQuery.class);
then(searchQueryRepository).should(times(1)).save(captor.capture());
SearchQuery persisted = captor.getValue();
assertThat(persisted.getStatus()).isEqualTo(ResultStatus.PROCESSING);
assertThat(persisted.getSearchType()).isEqualTo(SearchType.VECTOR);
assertThat(persisted.getQueryEmbeddingModel()).isSameAs(model);
assertThat(persisted.getTopK()).isEqualTo(SearchQueryFixture.TOP_K);
assertThat(result.getStatus()).isEqualTo(ResultStatus.PROCESSING);
🤖 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/SearchQueryCommandServiceTest.java`
around lines 36 - 46, Update the createProcessing test around
searchQueryRepository.save and SearchQueryCommandService.createProcessing to
capture the actual save argument with ArgumentCaptor<SearchQuery> instead of
asserting only the stubbed return value. Verify the captured SearchQuery has
PROCESSING status, the expected searchType, model, vector, and topK, and add the
required ArgumentCaptor and SearchType imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kangcheolung
kangcheolung merged commit 976c347 into develop Jul 22, 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] 질문 임베딩 + search_queries 저장

1 participant