간단히 말해, 챗봇 API를 안정적인 리뷰 결과 계약(review-finding contract)을 중심으로 정의하고, 임베딩(embedding), 검색(retrieval), 생성(generation)은 교체 가능한 어댑터 뒤에 배치해야 합니다. 학생의 코드를 검토하는 에듀테크 앱의 경우, 유용한 경계는 특정 벤더의 SDK가 아닙니다. 그것은 질의(query)가 순위가 매겨진 증거 세트(ranked set of evidence)가 되고, 이후 유형화된 결과물(typed finding)이 되는 지점입니다.
| 결정 | 선호하는 방식 | 이유 |
|---|
| 검색 경계 (Retrieval boundary) | 제공자 중립적 어댑터 (Provider-neutral adapter) | 인덱스나 임베딩 서비스를 교체해도 챗 계약은 변하지 않는다 |
| ... | | |
| 내 규칙은 간단합니다. 모델은 교체가 가능하게 만들되, 리뷰 증거는 검사할 수 있게 만들어야 합니다. 이것이 원맨 SaaS가 검색 변경 후 UI와 테스트를 다시 작성하는 데 일주일을 쓰는 것을 막아주는 부분입니다. | | |
챗봇 API는 실제로 무엇을 약속해야 하는가?
학생이 풀 리퀘스트(pull request) 또는 에디터 변경 사항을 제출합니다. 인앱 챗봇은 “왜 이 함수가 안전하지 않은가?”와 같은 질문에 답하고, 교사가 검토할 수 있는 구조화된 결과물(structured findings)을 반환합니다. API는 어떤 검색 엔진이 그것을 생성했는지보다 그 결과물의 형태와 의미를 약속해야 합니다.
유용한 응답은 답변, 제출된 코드 또는 강의 자료에 대한 인용(citations), 그리고 기계가 읽을 수 있는 결과물로 구성되어야 합니다. 또한 요청 ID(request ID)도 필요합니다. 이 ID는 사용자에게 보이는 답변과 로그, 재시도, 나중 평가를 연결하는 스레드입니다.
export type ReviewFinding = {
id: string;
severity: "info" | "warning" | "error";
...
incomplete 비트가 중요합니다. 하나의 검색 샤드(retrieval shard)가 시간 초과된 후 권위적으로 들리는 답변을 반환하는 챗봇은, 증거 세트가 부분적임을 인정하는 챗봇보다 운영하기가 더 어렵습니다. 이것은 운송 상세 문제일 뿐만 아니라 제품 결정이기도 합니다. 교사들은 언제 스스로 코드를 검사해야 하는지 알아야 합니다.
검색 쿼리는 사용자에게 도움이 되지 않는 한 공개 응답에서 제외하는 것이 좋습니다. 대신, 정규화된 쿼리(normalized query), 필터(filters), 임베딩 모델 식별자(embedding model identifier), 인덱스 버전(index version), 그리고 반환된 문서 ID를 내부적으로 기록해야 합니다. 이러한 필드는 클라이언트가 구현에 종속되지 않으면서도 잘못된 답변을 재현할 수 있게 해줍니다.
코드 리뷰에서 시맨틱 검색이 경계가 필요한 이유는 무엇인가요?
코드 리뷰에는 두 가지 어색한 데이터 유형이 있습니다. 소스 코드는 정확하지만 국소적입니다. 과정 가이드는 광범위하지만 과제 전반에 걸쳐 재사용되는 경우가 많습니다. 시맨틱 검색은 이러한 다양한 문서들에서 관련 자료를 찾는 데 능숙하지만, 유사성이 곧 증거는 아닙니다. 높은 점수는 결함을 유발한 정확한 라인을 놓치면서도 근처의 개념을 가리킬 수 있습니다.
따라서 리트리벌 어댑터(retrieval adapter)는 결론이 아닌 증거를 반환해야 합니다. 최소한의 인터페이스만으로 호스팅된 검색 API, 자체 관리 인덱스, 또는 벡터 지원 관계형 데이터베이스 모두에서 작동할 수 있어야 합니다.
단순하게 유지하세요.
export type SearchFilter = {
courseId: string;
assignmentId?: string;
...
어댑터가 제공자별 세부 정보(provider-specific details): 벡터 차원, 필터 구문, 페이지네이션, 재시도 헤더 등을 소유합니다. 리뷰 서비스는 제한 사항과 해석을 소유합니다. 이러한 분리는 검색 마이그레이션이 모든 라우트 핸들러로 새어 나가는 것을 방지합니다. 또한 현재 과정 외부의 문서를 요청하는 쿼리처럼 안전하지 않은 요청을 거부할 수 있는 단일 지점을 제공합니다.
똑같이 중요한, 덜 화려하지만 필수적인 경계가 있습니다: 문서 식별자(document identity)입니다. 코드를 인덱싱할 때 안정적인 documentId, 소스 리비전(source revision), 그리고 라인 또는 문자 오프셋을 저장해야 합니다. 청크 분할 방식이 변경되더라도, 이전의 발견 사항은 여전히 설명 가능해야 합니다. 출처가 없는 스니펫은 주소(address)가 없는 스크린샷과 같습니다.
요청 흐름은 실제 실패를 어떻게 처리해야 하나요?
파일이 짧은 경우 동기식 요청(synchronous request)으로 충분할 수 있습니다. 하지만 리포지토리 전체의 경우에는 인덱싱과 리뷰를 별도의 작업(jobs)으로 분리해야 합니다. 채팅 경로는 리뷰 요청을 생성하고, 가장 최근에 완료된 인덱스를 검색하며, 그 상태를 보고할 수 있습니다. 이렇게 하면 브라우저 요청이 모든 비용이 많이 드는 작업을 책임지게 되는 것을 방지할 수 있습니다.
제가 디자인 목표로 사용하는 플로우는 다음과 같습니다:
- 테넌트(tenant), 과정(course), 과제(assignment), 소스 리비전(source revision)을 검증합니다.
- 리비전과 리뷰 정책을 키로 하는 비멱등성 요청(idempotent request)을 생성합니다.
- 명시적인 필터가 적용된 유한 증거 세트(bounded evidence set)를 검색합니다.
- 생성 계층(generation layer)에 찾기 스키마(finding schema)와 일치하는 JSON을 요청합니다.
- 반환하기 전에 해당 JSON을 검증하고 증거 ID를 첨부합니다.
공급자 중립적인 런타임(provider-neutral runtime)은 속도 제한(throttling)을 일반적인 “AI 실패” 메시지가 아닌 상태 전이(state transition)로 취급해야 합니다. HTTP 429는 서버가 요청을 제한하고 있다는 의미이며, 클라이언트는 제공되는 경우 Retry-After 헤더를 검사하여 유한 백오프(bounded backoff)를 적용할 수 있습니다. 영원히 재시도해서는 안 되며, 요청 키 없이 비멱등성 작업을 재시도해서도 안 됩니다.
const delay = (attempt: number): number =>
Math.min(8_000, 250 * 2 ** attempt);
...
여기서 중요한 부분은 이 헬퍼 주변의 정책입니다. 검색 읽기 작업은 보통 재시도가 가능합니다. 리뷰 레코드를 생성하는 요청에는 비멱등성 키가 필요합니다. 생성 호출(generation call)은 출력이 안전하게 대체될 수 있고 요청 예산(request budget)이 여전히 허용할 때만 재시도해야 합니다. 세 번의 시도는 정책적 예시일 뿐이며, 모든 서비스가 동일한 횟수를 사용해야 한다는 약속은 아닙니다.
각 경계마다 타임아웃을 사용하십시오. 지속 시간(duration), 시도 횟수(attempt number), 결과 개수(result count), 인덱스 리비전, 그리고 마스킹된 요청 ID를 기록하십시오. 기본적으로 학생 소스는 로깅하지 마십시오. 1인 팀은 두 번째 개인 정보 문제를 야기하는 관측 가능성 시스템(observability system)을 감당할 수 없습니다.
공급자를 선택하지 않고 구현체를 비교하려면 어떻게 해야 하나요?
기능 체크리스트가 아닌 경계와 운영 동작을 비교해야 합니다. 호스팅되는 벡터 검색 서비스는 유지 관리를 줄일 수 있지만 자체적인 필터, 내보내기(export), 또는 인덱스 버전 제약을 부과합니다. 셀프-호스팅 검색 스택은 배포 및 데이터 지역성(data locality)에 대한 더 많은 제어권을 제공하지만, 업그레이드, 용량 계획(capacity planning), 온콜 작업(on-call work)을 추가합니다.
Postgres 기반 벡터 확장은 메타데이터와 검색 기능을 애플리케이션 데이터 근처에 유지하여 트랜잭션을 단순화할 수 있지만, 검색 동작 및 스케일링은 여전히 워크로드(workload)를 대상으로 테스트가 필요합니다.
구체적인 참고 지점을 위해, Pinecone은 관리형 벡터 인덱스(managed vector index)를 노출하며, Weaviate는 오픈 소스 데이터베이스로 배포되거나 관리형 서비스로 사용될 수 있고, pgvector는 Postgres 내에 벡터 컬럼과 최근접 이웃(nearest-neighbor) 쿼리를 유지합니다. 이러한 사실들은 서로 다른 소유 표면(ownership surfaces)을 설명할 뿐, 승자를 결정하지 않습니다. 어댑터는 여전히 필터, 결과 메타데이터, 삭제 동작, 인덱스 개정판(index revisions)을 정규화해야 합니다.
2020-12 JSON 스키마(JSON Schema) 방언과 OpenAPI 3.1.1 계약을 인터페이스 제약 조건으로 간주하고, 각 구현체를 동일한 테스트용 데이터(fixtures)로 테스트해야 합니다.
이것들은 서로 다른 트레이드오프입니다. 어느 것도 보편적으로 최고는 아닙니다.
찾아야 할 첫 번째 실패 사례는 잘못된 근거를 가진 그럴듯한 답변입니다.
각 어댑터를 통해 동일한 코퍼스(corpus)를 테스트하세요. 실제 리뷰 질문으로 만든 작은 평가 세트를 사용합니다: 누락된 null 체크, 잘못된 권한 부여 조건, 그리고 일반적인 스타일 제안과 충돌하는 과정 규칙 등이 있습니다. 관련 근거의 재현율(recall), 반환되는 청크(chunks)의 정밀도(precision), 선택된 한계에서의 지연 시간(latency), 그리고 스키마 유효성 검사에 실패하는 응답 비율을 측정하세요. 이러한 측정값들을 모델 품질과 분리하여 유지해야 합니다. 그렇지 않으면 검색 회귀(retrieval regression)가 프롬프트 회귀(prompt regression)처럼 보일 것입니다.
여기서 '이식성(Portability)'은 실질적인 정의를 가집니다. 문서 ID, 필터, 소스 개정판(source revisions), 그리고 ReviewFinding 계약을 보존하면서 임베딩(embedding) 또는 인덱스 구현 방식을 변경할 수 있어야 합니다. 제공업체(providers)를 변경하는 것이 UI, 프롬프트 형식, 데이터베이스 레코드까지 함께 변경해야 한다면, 해당 어댑터는 경계(boundary)가 아닙니다. 그것은 SDK 래퍼일 뿐입니다.
차선책 접근 방식은 운영 단순성(operational simplicity)이 주된 제약 조건일 때 더 좋습니다. 검색 인덱스를 내보낼 필요가 없고 범위가 좁은 코퍼스(corpus)를 가진 소규모 팀에게는 하나의 관리형 검색 시스템(managed retrieval system)을 선택하는 것이 적절할 수 있습니다. 반대로, 과정 데이터가 통제된 환경에 남아 있어야 하거나 색인화 동작(indexing behavior)에 직접 접근해야 할 때는 자체 관리 또는 데이터베이스 로컬 방식이 더 매력적입니다. 이러한 결정은 보존(retention), 필터, 내보내기(export), 디버깅 요구 사항을 바탕으로 내려야 합니다.
이번 주에 출시할 결정
하나의 공개 채팅 엔드포인트와 하나의 내부 SemanticRetriever 인터페이스로 시작하십시오. 더 많은 제공업체를 추가하기 전에 찾기 스키마(finding schema)를 버전 관리하세요. 소스 개정판과 증거 ID(evidence IDs)를 영구 저장합니다. 세 가지 검색 실패 및 하나의 HTTP 429 응답에 대한 목업(fixtures)을 추가하십시오. 그런 다음 동일한 검토 질문들을 CI에서 모든 어댑터를 통해 실행하십시오.
이것만으로도 주간 출시가 가능한 충분한 구조입니다. 차별화되지 않은 제공업체 플러밍(provider plumbing, 기술적 구현 부분)은 하나의 이음매 뒤에 숨겨지고, 제품 작업(product work)은 명확하게 보존됩니다. 즉, 학생들에게 더 명확한 피드백을 제공하고 교사들의 검토 분류 작업을 줄이는 것입니다. 얻게 되는 가치는 완벽한 답의 약속이 아닙니다. 그것은 대화, 증거 추적(evidence trail), 또는 해당 기능을 신뢰할 수 있게 만드는 테스트를 무효화하지 않으면서 검색을 개선할 수 있는 능력입니다.
참고 자료