Skip to content

[Feat] Cohere Embed API 클라이언트 1차 연동 - #227

Merged
whc9999 merged 1 commit into
devfrom
feat/cohere-embed-client
Jul 29, 2026
Merged

[Feat] Cohere Embed API 클라이언트 1차 연동#227
whc9999 merged 1 commit into
devfrom
feat/cohere-embed-client

Conversation

@whc9999

@whc9999 whc9999 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

✨ 어떤 이유로 PR를 하셨나요?

  • feature 병합
  • 버그 수정(아래에 issue #를 남겨주세요)
  • 코드 개선
  • 코드 수정
  • 배포
  • 기타(아래에 자세한 내용 기입해주세요)

📋 세부 내용 - 왜 해당 PR이 필요한지 작업 내용을 자세하게 설명해주세요

  • Cohere 설정 프로퍼티와 RestClient 기반 embedding client 추가
  • /v2/embed 요청/응답 DTO 및 search_document/search_query 호출 분리
  • API key, 입력, 응답 개수 및 1024차원 검증 로직 추가
  • Cohere API 오류를 기존 GeneralException 체계로 변환
  • 외부 API 호출 없는 단위 테스트와 수동 검증 문서 추가

📸 작업 화면 스크린샷

⚠️ PR하기 전에 확인해주세요

  • 로컬테스트를 진행하셨나요?
  • 머지할 브랜치를 확인하셨나요?
  • 관련 label을 선택하셨나요?

🚨 관련 이슈 번호 [ ]

Summary by CodeRabbit

  • 새 기능

    • Cohere Embed API를 활용한 문서 및 검색어 임베딩 생성을 지원합니다.
    • 기본 모델, 벡터 차원, 입력 유형 및 네트워크 타임아웃 설정을 제공합니다.
    • API 오류와 응답 검증 결과를 구분해 처리합니다.
  • 문서

    • API 키 설정 방법과 로컬 임베딩 검증 절차를 README에 추가했습니다.
    • 기본 테스트가 외부 Cohere API를 호출하지 않는다는 안내를 포함했습니다.
  • 테스트

    • 정상 임베딩 생성, 입력 검증, 오류 응답 및 벡터 차원 검증 테스트를 추가했습니다.

- Cohere 설정 프로퍼티와 RestClient 기반 embedding client 추가
- /v2/embed 요청/응답 DTO 및 search_document/search_query 호출 분리
- API key, 입력, 응답 개수 및 1024차원 검증 로직 추가
- Cohere API 오류를 기존 GeneralException 체계로 변환
- 외부 API 호출 없는 단위 테스트와 수동 검증 문서 추가
@whc9999 whc9999 self-assigned this Jul 29, 2026
@whc9999 whc9999 added the ✨ feat New feature or request label Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Cohere Embed API 연동을 위한 설정 바인딩, 요청·응답 DTO, 임베딩 클라이언트가 추가되었습니다. 클라이언트는 입력과 응답을 검증하고 HTTP 오류를 공통 예외로 변환하며, 스텁 서버 기반 테스트와 로컬 검증 문서가 포함됩니다.

Changes

Cohere 임베딩 연동

Layer / File(s) Summary
설정 및 API 계약
src/main/java/com/jobdri/jobdri_api/global/cohere/CohereProperties.java, src/main/java/com/jobdri/jobdri_api/global/cohere/dto/*, src/main/java/com/jobdri/jobdri_api/global/cohere/CohereConfig.java, src/main/resources/application-*.yaml
cohere.* 설정의 기본값과 환경별 구성을 추가하고, Cohere 요청·응답 JSON을 표현하는 레코드 DTO와 설정 바인딩을 정의합니다.
임베딩 호출 및 응답 변환
src/main/java/com/jobdri/jobdri_api/global/cohere/CohereEmbeddingClient.java
문서·쿼리 임베딩 요청을 생성하고 /v2/embed를 호출하며, 입력·상태 코드·응답 개수·벡터 차원을 검증한 뒤 float[]로 변환합니다.
스텁 기반 동작 검증과 운영 안내
src/test/java/com/jobdri/jobdri_api/global/cohere/CohereEmbeddingClientTest.java, README.md
로컬 HTTP 서버로 정상 요청과 오류 매핑을 검증하고, API 키 설정 및 수동 임베딩 검증 절차를 문서화합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant 호출자
  participant CohereEmbeddingClient
  participant Cohere Embed API
  호출자->>CohereEmbeddingClient: embedDocuments 또는 embedQuery 호출
  CohereEmbeddingClient->>CohereEmbeddingClient: API 키와 입력 검증
  CohereEmbeddingClient->>Cohere Embed API: POST /v2/embed
  Cohere Embed API-->>CohereEmbeddingClient: 임베딩 응답 또는 HTTP 오류
  CohereEmbeddingClient-->>호출자: float[] 또는 List<float[]> 반환
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 Cohere Embed API 클라이언트 1차 연동이라는 핵심 변경을 간결하게 잘 요약합니다.
Description check ✅ Passed 템플릿의 주요 섹션과 체크리스트를 대부분 채웠고, 작업 내용도 변경사항과 잘 맞습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 feat/cohere-embed-client

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: 4

🤖 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/jobdri/jobdri_api/global/cohere/CohereEmbeddingClient.java`:
- Around line 31-38: Update the requestFactory used by CohereEmbeddingClient to
use a connection-pooling HTTP client such as
HttpComponentsClientHttpRequestFactory instead of
SimpleClientHttpRequestFactory. Configure pooled connection limits and preserve
the existing embedding connect and read timeout values from CohereProperties.
- Around line 77-91: Update the Cohere request flow around the 429/5xx handler
to retry transient failures with bounded exponential backoff before converting
them to SERVICE_UNAVAILABLE. Honor the Retry-After response header when present,
while preserving the existing invalidParameter handling for 4xx client errors
and the final unavailable error after retries are exhausted.

In `@src/main/resources/application-prod.yaml`:
- Around line 174-175: Remove the unused nested api.key block under cohere from
application-prod.yaml (174-175), application-analysis-eval.yaml (96-97), and
application-dev.yaml (173-174); retain the supported cohere.api-key
configuration unchanged.

In
`@src/test/java/com/jobdri/jobdri_api/global/cohere/CohereEmbeddingClientTest.java`:
- Around line 107-178: CohereEmbeddingClient의 callCohere timeout 처리 경로를 검증하는
테스트를 추가하세요. 기존 테스트 헬퍼인 TestCohereServer를 활용해 readTimeout보다 긴 지연 후 응답하도록 구성하고,
client.embedQuery 또는 embedDocuments가 GeneralException을 발생시키며 코드가
EXTERNAL_SERVICE_TIMEOUT인지 확인하세요.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: b574183f-d610-4a22-9b2e-faf47bbe3c5b

📥 Commits

Reviewing files that changed from the base of the PR and between 4f32a8a and 79537b1.

⛔ Files ignored due to path filters (2)
  • evaluation/evaluation_nlg_judge_missing_keyword_provenance.csv is excluded by !**/*.csv
  • evaluation/evaluation_nlg_judge_missing_keyword_provenance_comparison.csv is excluded by !**/*.csv
📒 Files selected for processing (10)
  • README.md
  • src/main/java/com/jobdri/jobdri_api/global/cohere/CohereConfig.java
  • src/main/java/com/jobdri/jobdri_api/global/cohere/CohereEmbeddingClient.java
  • src/main/java/com/jobdri/jobdri_api/global/cohere/CohereProperties.java
  • src/main/java/com/jobdri/jobdri_api/global/cohere/dto/CohereEmbeddingRequest.java
  • src/main/java/com/jobdri/jobdri_api/global/cohere/dto/CohereEmbeddingResponse.java
  • src/main/resources/application-analysis-eval.yaml
  • src/main/resources/application-dev.yaml
  • src/main/resources/application-prod.yaml
  • src/test/java/com/jobdri/jobdri_api/global/cohere/CohereEmbeddingClientTest.java

Comment on lines +31 to +38
public CohereEmbeddingClient(CohereProperties properties, RestClient.Builder restClientBuilder) {
this.properties = properties;
this.restClient = restClientBuilder
.baseUrl(properties.baseUrl())
.requestFactory(requestFactory(properties))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

SimpleClientHttpRequestFactory는 커넥션 풀링을 제공하지 않습니다.

SimpleClientHttpRequestFactory는 JDK HttpURLConnection 기반으로 요청마다 새 연결을 열고 닫는 구조라 TCP/TLS 핸드셰이크 비용이 매번 발생하고, 커넥션 풀 크기 등을 세밀하게 제어할 수 없습니다. corpus 적재처럼 대량으로 embedDocuments를 호출하는 시나리오에서는 처리량 저하 요인이 될 수 있습니다. HttpComponentsClientHttpRequestFactory(Apache HttpClient) 등 풀링을 지원하는 팩토리로 교체하는 것을 권장합니다.

As per path instructions, "외부 API 호출 성능: connection pooling ... 대량 요청 시 처리량 저하 가능성"을 우선순위 높게 검토해야 합니다.

♻️ 커넥션 풀링을 지원하는 팩토리로 교체하는 예시
private static ClientHttpRequestFactory requestFactory(CohereProperties properties) {
    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
    connectionManager.setMaxTotal(50);
    connectionManager.setDefaultMaxPerRoute(20);

    RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(Timeout.ofMilliseconds(properties.embedding().connectTimeout().toMillis()))
            .setResponseTimeout(Timeout.ofMilliseconds(properties.embedding().readTimeout().toMillis()))
            .build();

    CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .setDefaultRequestConfig(requestConfig)
            .build();

    return new HttpComponentsClientHttpRequestFactory(httpClient);
}

Also applies to: 164-169

🤖 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/jobdri/jobdri_api/global/cohere/CohereEmbeddingClient.java`
around lines 31 - 38, Update the requestFactory used by CohereEmbeddingClient to
use a connection-pooling HTTP client such as
HttpComponentsClientHttpRequestFactory instead of
SimpleClientHttpRequestFactory. Configure pooled connection limits and preserve
the existing embedding connect and read timeout values from CohereProperties.

Source: Path instructions

Comment on lines +77 to +91
.onStatus(
status -> status.value() == 429 || status.is5xxServerError(),
(ignoredRequest, ignoredResponse) -> {
throw unavailable("Cohere Embed API가 일시적으로 응답할 수 없습니다.");
}
)
.onStatus(
status -> status.value() == 400
|| status.value() == 401
|| status.value() == 403
|| status.is4xxClientError(),
(ignoredRequest, ignoredResponse) -> {
throw invalidParameter("Cohere Embed API 요청 또는 설정이 올바르지 않습니다.");
}
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

429/5xx에 대한 재시도(backoff) 정책이 없습니다.

현재는 429/5xx 응답을 즉시 SERVICE_UNAVAILABLE로 변환해 실패시킵니다. Cohere는 일시적인 rate limit(429)에 대해 지수 백오프 재시도를 권장하는 사례가 흔한데, 이 구현은 상위 호출자에게 즉시 예외를 전파합니다. 1차 연동 범위상 당장 필수는 아니지만, 향후 실호출 트래픽이 늘어나면 429에서 재시도(가능하면 Retry-After 헤더 반영)를 추가하는 것을 권장합니다.

As per path instructions, "외부 API 호출 성능: ... 재시도 정책, rate limit 대응"을 우선순위 높게 검토해야 합니다.

🤖 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/jobdri/jobdri_api/global/cohere/CohereEmbeddingClient.java`
around lines 77 - 91, Update the Cohere request flow around the 429/5xx handler
to retry transient failures with bounded exponential backoff before converting
them to SERVICE_UNAVAILABLE. Honor the Retry-After response header when present,
while preserving the existing invalidParameter handling for 4xx client errors
and the final unavailable error after retries are exhausted.

Source: Path instructions

Comment on lines 174 to 175
api:
key: ${COHERE_API_KEY:}

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 | 🔵 Trivial | ⚡ Quick win

세 환경 yaml 모두에 미사용 cohere.api.key 중첩 설정이 중복 존재합니다. CohereProperties record는 apiKey(→cohere.api-key), baseUrl, embedding 필드만 가지고 있어 cohere.api.key 중첩 값은 어디에도 바인딩되지 않는 죽은 설정이며, cohere.api-key와 동일한 값(${COHERE_API_KEY:})을 중복 노출해 향후 유지보수 시 혼란을 유발할 수 있습니다.

  • src/main/resources/application-prod.yaml#L174-L175: api.key 중첩 블록 삭제.
  • src/main/resources/application-analysis-eval.yaml#L96-L97: api.key 중첩 블록 삭제.
  • src/main/resources/application-dev.yaml#L173-L174: api.key 중첩 블록 삭제.
📍 Affects 3 files
  • src/main/resources/application-prod.yaml#L174-L175 (this comment)
  • src/main/resources/application-analysis-eval.yaml#L96-L97
  • src/main/resources/application-dev.yaml#L173-L174
🤖 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/resources/application-prod.yaml` around lines 174 - 175, Remove the
unused nested api.key block under cohere from application-prod.yaml (174-175),
application-analysis-eval.yaml (96-97), and application-dev.yaml (173-174);
retain the supported cohere.api-key configuration unchanged.

Comment on lines +107 to +178
@Test
@DisplayName("Cohere 429와 5xx는 서비스 일시 장애 예외로 변환한다")
void transientCohereErrors() throws Exception {
for (int status : List.of(429, 500)) {
try (TestCohereServer server = startServer(status, "{\"message\":\"temporary\"}", new AtomicReference<>())) {
CohereEmbeddingClient client = client(server.baseUrl(), "test-api-key", 3);

assertThatThrownBy(() -> client.embedDocuments(List.of("text")))
.isInstanceOfSatisfying(GeneralException.class, exception ->
assertThat(exception.getCode()).isEqualTo(GeneralErrorCode.SERVICE_UNAVAILABLE));
}
}
}

@Test
@DisplayName("Cohere 400, 401, 403은 요청 또는 설정 오류로 변환한다")
void requestOrConfigurationErrors() throws Exception {
for (int status : List.of(400, 401, 403)) {
try (TestCohereServer server = startServer(status, "{\"message\":\"bad request\"}", new AtomicReference<>())) {
CohereEmbeddingClient client = client(server.baseUrl(), "test-api-key", 3);

assertThatThrownBy(() -> client.embedDocuments(List.of("text")))
.isInstanceOfSatisfying(GeneralException.class, exception ->
assertThat(exception.getCode()).isEqualTo(GeneralErrorCode.INVALID_PARAMETER));
}
}
}

@Test
@DisplayName("응답 embedding 개수가 요청 texts 개수와 다르면 예외 처리한다")
void mismatchedEmbeddingCount() throws Exception {
try (TestCohereServer server = startServer(200, responseJson(3, 1), new AtomicReference<>())) {
CohereEmbeddingClient client = client(server.baseUrl(), "test-api-key", 3);

assertThatThrownBy(() -> client.embedDocuments(List.of("first", "second")))
.isInstanceOfSatisfying(GeneralException.class, exception ->
assertThat(exception.getCode()).isEqualTo(GeneralErrorCode.SERVICE_UNAVAILABLE))
.hasMessageContaining("개수");
}
}

@Test
@DisplayName("응답 embedding 차원이 설정 차원과 다르면 예외 처리한다")
void mismatchedEmbeddingDimension() throws Exception {
try (TestCohereServer server = startServer(200, responseJson(2, 1), new AtomicReference<>())) {
CohereEmbeddingClient client = client(server.baseUrl(), "test-api-key", 3);

assertThatThrownBy(() -> client.embedQuery("query"))
.isInstanceOfSatisfying(GeneralException.class, exception ->
assertThat(exception.getCode()).isEqualTo(GeneralErrorCode.SERVICE_UNAVAILABLE))
.hasMessageContaining("차원");
}
}

@Test
@DisplayName("응답 body가 없거나 embeddings가 비어 있으면 예외 처리한다")
void emptyResponseBodyOrEmbeddings() throws Exception {
try (TestCohereServer server = startServer(200, "", new AtomicReference<>())) {
CohereEmbeddingClient client = client(server.baseUrl(), "test-api-key", 3);

assertThatThrownBy(() -> client.embedQuery("query"))
.isInstanceOfSatisfying(GeneralException.class, exception ->
assertThat(exception.getCode()).isEqualTo(GeneralErrorCode.SERVICE_UNAVAILABLE));
}
try (TestCohereServer server = startServer(200, "{\"embeddings\":{\"float\":[]}}", new AtomicReference<>())) {
CohereEmbeddingClient client = client(server.baseUrl(), "test-api-key", 3);

assertThatThrownBy(() -> client.embedQuery("query"))
.isInstanceOfSatisfying(GeneralException.class, exception ->
assertThat(exception.getCode()).isEqualTo(GeneralErrorCode.SERVICE_UNAVAILABLE));
}
}

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 | 🔵 Trivial | ⚡ Quick win

timeout(ResourceAccessExceptionEXTERNAL_SERVICE_TIMEOUT) 시나리오 테스트가 없습니다.

429/5xx, 400/401/403, 응답 개수/차원 불일치, 빈 응답 등 에러 매핑은 잘 검증되어 있지만, CohereEmbeddingClientconnectTimeout/readTimeout 초과 시 ResourceAccessExceptionEXTERNAL_SERVICE_TIMEOUT으로 변환하는 로직(callCohere의 catch 블록)을 검증하는 테스트가 없습니다. 응답을 지연시키는 스텁 서버(예: readTimeout보다 긴 sleep 후 응답)로 이 경로를 커버하면 회귀 위험을 줄일 수 있습니다.

As per path instructions, "테스트가 동시성, 재시도, timeout, 실패 복구, 멱등성 같은 비동기 시나리오를 충분히 검증하는지 중점적으로 확인하세요. 해피패스만 있는 경우 경계 조건과 회귀 위험을 지적하세요."

🤖 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/jobdri/jobdri_api/global/cohere/CohereEmbeddingClientTest.java`
around lines 107 - 178, CohereEmbeddingClient의 callCohere timeout 처리 경로를 검증하는
테스트를 추가하세요. 기존 테스트 헬퍼인 TestCohereServer를 활용해 readTimeout보다 긴 지연 후 응답하도록 구성하고,
client.embedQuery 또는 embedDocuments가 GeneralException을 발생시키며 코드가
EXTERNAL_SERVICE_TIMEOUT인지 확인하세요.

Source: Path instructions

@whc9999
whc9999 merged commit 00d9785 into dev Jul 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feat New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant