Conversation
Feat: Rule Candidate Mining(#40)
…at/autoPipeline
Feat : 자동 분석 파이프라인 및 진행 상태 조회 기능 추가
[Refactor/#45] Rule candidate 노출 정책/품질 라벨/재마이닝(forceRebuild) 전략 개선
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Feat: LLM API 개발
feat: LLM API 개발 기능
…at/autoPipeline
Feat : Rule, LLM 자동 파이프라인 추가
chore:graphstore 인덱스 백업에 artifact 최신 조회 인덱스 추가
refactor: 군집화 안정성, 품질 및 재현성 개선
Refactor/#83: 의미그래프 기반 API/Rule 뒷단 파이프라인 보강
develop(#89, refactor/#83)의 LLM 토글·HTTP 엔드포인트 시드·semantic edge trust와 feat/#84의 채움말 제거·품질 게이트 2축 분리를 합친다. 충돌 5파일 7블록의 해결 근거: - RunPipelineExecutor: develop의 ossdoc.llm.enabled 토글을 채택했다. #84의 하드코딩 비활성화와 목적이 같고 설정으로 되돌릴 수 있어 상위호환이다. LLM_ENABLED 기본값이 false라 "지금은 돌지 않는다"는 전제도 유지된다. #84 주석에만 있던 READY 캐시 미발행 설명은 살려서 옮겼다. - LlmInputAssemblerBuildSupport: endpointSeed는 살리고 inferMethodUsage 폴백만 버렸다. #84가 제거한 것은 메서드 이름으로 문장을 지어내던 추측이고, endpointSeed는 HANDLES_ENDPOINT 엣지(HTTP 메서드 + 경로)에서 나온 근거다. summarySeed는 프롬프트가 근거로 못 박는 채널이므로 둘을 같이 취급할 수 없다. normalizeSummarySeed가 빈 입력에 FILLER_EXACT 등재 문구를 채워 넣으므로 빈 값 가드를 두고, 경로가 없는 저해상도 폴백은 EMPTY로 좁혔다. - LlmServiceBuildSupport: buildGuide가 8인자로 바뀌었으므로 whenToUse 갈래는 성립하지 않는다. 세 번째 블록은 git 정렬이 어긋나 HEAD의 slotEvidence 생성과 attachUsageScenario 호출이 병합 결과에서 통째로 빠졌다. 컴파일은 통과하고 산출물 JSON에서만 드러나는 유실이라 복원했다. - application.yml / LlmServiceBuildSupportTest: 양쪽이 서로 다른 것을 더한 additive 충돌이라 둘 다 유지했다. 병합 후 드러난 의미 충돌 하나를 함께 고쳤다. LlmInputAssemblerBuildSupportSemanticEdgeTest는 develop이 가져온 새 파일이라 충돌 없이 자동 병합됐지만, #84가 늘린 2인자 생성자를 몰라 compileTestJava가 깨졌다. 같은 패키지의 ScenarioSeedTest와 같은 방식으로 맞췄다. 검증: compileJava/compileTestJava 통과, test 415개 전부 통과(실패 0), 앱 기동 확인(:8080). 기준선 영향 없음 - junit-framework run의 api_map.json은 entry_methods 712개 중 http_endpoints 보유가 0이라 endpointSeed는 전 구간 EMPTY로 떨어진다.
symbolId는 애플리케이션이 부여하는 값이라 Spring Data의 기본 판별(id == null)로는 신규 여부를 알 수 없어 save()가 항상 merge()로 동작했다. merge()는 관리 사본을 반환하므로 저장에 넘긴 원본 인스턴스는 detached 상태로 남고, GraphStoreIngestService가 saveAll 이후에 수행하는 owner/source span 연결이 DB에 반영되지 않았다. Persistable<String>을 구현해 신규 여부를 직접 알려 persist() 경로를 타게 한다. 조회(@PostLoad)와 저장(@PostPersist) 직후에는 신규가 아니므로 플래그를 뒤집는다. 검증: junit-framework 신규 run이 회귀 이전 baseline과 동일한 수치를 재현했다. (symbol 13,106 / owner 12,067 / source_file 13,032 / start line 8,819)
계약 형태만 갖추고 값이 전부 빈 문자열인 응답("빈 봉투")이 기존 방어선을 모두 통과했다.
파싱은 성공하고 step도 stepNo로 정상 매칭되므로 logSeedAxisDrops의 폐기 수가 0이라,
"골격 채움 0/N" INFO 한 줄만 남긴 채 시드 문구만 실린 시나리오가 산출물로 나갔다.
junit-framework 실측에서 접근자만 나열된 시나리오 두 장(SCN-005/006)이 이 경로로 빠져
20 step 중 8 step이 빈 채로 나갔고, 서술 채움이 62/180(34%)에 머물렀다.
normalizeOneScenario에서 step 서술이 하나도 없으면 RESPONSE_PARSE_FAILED를 던져
generateWithRetryPlan의 compact 재시도를 쓰고, 그래도 비면 그 한 장만 골격으로 떨어진다.
판정은 description과 STEP_RICH_TEXT_FIELDS를 함께 보므로, description은 비우고 action만
쓴 정상 응답은 막지 않는다. 0칸 채움 로그는 INFO에서 WARN으로 올린다.
claim이 잡는 lock은 30분인데 LLM 단계 하나가 그보다 오래 걸린다(commons-cli 실측 83분). 갱신이 없으면 claim SQL의 "RUNNING + locked_until < now" 조건에 걸려 아직 실행 중인 job을 다른 인스턴스가 다시 집어가 중복 실행한다. heartbeat를 @scheduled에 얹을 수 없다. worker가 스케줄러 스레드에서 executor.execute()를 동기 호출하고 스케줄러 풀 크기가 기본 1이라, job이 도는 동안 @scheduled는 하나도 못 돈다. 그래서 데몬 스레드 하나를 따로 두고 실행을 감싸는 동안에만 heartbeat를 켠다(기본 10분 주기). renewLock은 소유자(lockedBy)와 상태(RUNNING)를 함께 확인한다. 이미 lock을 잃었거나 markCacheWaiting으로 lock이 비워진 job을 되살리면 오히려 중복 실행을 만들기 때문이다. 검증: commons-cli run에서 LLM이 38분을 쓰는 동안 locked_until이 계속 연장돼 (locked_at 13:54:50 / locked_until 14:54:50) 85분 내내 lock이 유지됐다.
"[임시] 모델 교체 작업 동안 실행하지 않고 SKIPPED로만 기록한다"는 설명이 더 이상 맞지 않는다. LLM 단계는 ossdoc.llm.provider=ollama(로컬 qwen)로 파이프라인 끝에 이어붙어 실행된다. "외부 API 토큰과 비용에 의존한다"는 문구도 provider에 따라 갈리므로 함께 고친다. 동작 코드는 변경하지 않았다. 활성화는 ossdoc.llm.enabled 설정이며 기본값은 false 유지다.
배포 환경에서도 LLM 단계가 파이프라인 끝에 붙어 실행되도록 ossdoc.llm.enabled 기본값을 true로 올린다. provider는 기존대로 ollama(로컬 qwen)다. output.local-only 기본값도 false로 함께 내린다. true면 워크스페이스 파일만 남기고 DB/S3 저장을 건너뛰는데, AnalysisCachePublishService.REQUIRED_READY_ARTIFACT_KINDS가 LLM 산출물 5종을 DB에서 조회하므로 READY 캐시가 발행되지 않고 artifact 조회 API로도 결과를 꺼낼 수 없다. enabled만 켜면 LLM이 돌고도 결과가 보이지 않는다. 전제: provider가 가리키는 모델에 실제로 닿아야 한다. ollama.base-url 기본값이 localhost:11434라 컨테이너 안에서는 자기 자신을 가리키므로, 배포 시 OLLAMA_BASE_URL을 접근 가능한 주소로 지정하거나 compose에 ollama 서비스를 추가해야 한다. 닿지 않으면 LLM은 선택 단계이므로 run이 PARTIAL_SUCCESS로 끝난다.
제공자를 run 단위로 고를 수 있게 하기 위한 자리를 먼저 만든다. RepoRun.llmProvider: - 제공자를 요청 시점에 고르면, 실제로 LLM이 도는 시점(파이프라인 워커)까지 그 선택을 실어 나를 곳이 필요하다. 요청과 실행이 비동기로 갈라져 있어 요청 객체로는 닿지 않는다. - nullable이다. 이 기능 이전 run과 제공자를 지정하지 않은 요청을 그대로 살리고, null이면 실행 시점에 설정 기본값이 쓰인다. - 생성자 파라미터 대신 assignLlmProvider()를 둔 이유는 기존 생성자 호출 지점 (운영 3곳 + 테스트 7곳)을 건드리지 않기 위해서다. assignAnalysisAccessType과 같은 방식이다. 캐시 키: - 같은 repo/commit이라도 제공자가 다르면 산출물이 다르다. 키에 없으면 claude로 요청한 run에 ollama가 만든 READY 번들이 그대로 나간다. - 지정 없이 만들어진 run은 null이고, 발행/조회 양쪽에서 같은 폴백 토큰으로 정규화되므로 키가 어긋나지 않는다. SymbolBatchSavePersistenceTest는 필드가 늘 때마다 깨지는 @AllArgsConstructor 대신 명시 생성자를 쓰도록 바꿨다.
기존에는 @ConditionalOnProperty로 구현 하나만 빈에 올려서, 기동 후에는 제공자를 바꿀 수 없었다. run 단위로 고르려면 두 구현이 모두 떠 있어야 하고, 고르는 책임을 둘 자리가 필요하다. - LlmChatClient에 provider()와 available()을 추가하고 조건부 등록을 걷어냈다. available()은 "빈으로 떠 있다"와 "쓸 수 있다"를 구분한다. claude는 API 키가 없으면 빈은 있지만 호출이 전부 401로 떨어지므로, 그 상태를 요청 시점에 걸러낸다. - LlmChatClientResolver가 provider로 구현을 찾는다. 지정이 없으면 ossdoc.llm.provider 설정값을 쓰므로 기존 동작과 같다. 설정값 자체가 이상하면 첫 run이 아니라 기동에서 실패한다. - LlmService.refine()은 진입부에서 클라이언트를 한 번만 확정해 5단계 전부에 넘긴다. 단계마다 다시 고르면 한 run의 산출물이 두 모델에서 나올 수 있다. - 인식할 수 없는 provider 값은 기본값으로 흘리지 않고 PROVIDER_NOT_AVAILABLE로 실패시킨다. 오타 하나로 의도와 다른 모델이 조용히 돌면 비용과 산출물 품질이 함께 어긋나고 캐시 키도 그 값으로 굳는다. 파이프라인은 run에 기록된 제공자를 실어 LlmService를 호출한다. job.getRun()은 지연 로딩 프록시라 워커 스레드에서 초기화가 보장되지 않으므로 저장소에서 다시 읽는다.
요청 경로: - POST /api/v1/runs가 llmProvider를 선택 필드로 받는다. 보내지 않으면 서버 설정값이 쓰이므로 기존 클라이언트는 수정 없이 그대로 동작한다. - 제공자는 캐시 키의 일부라 캐시 조회보다 먼저 확정한다. 여기서 확정하면 쓸 수 없는 제공자 (예: 키 없는 claude)를 요청한 경우 80분짜리 파이프라인이 아니라 이 요청에서 400으로 실패한다. 캐시 격리: - DB 폴백 조회는 cacheKey가 아니라 repo/commit으로 찾기 때문에, 키에 제공자 축을 넣는 것만으로는 재사용을 막지 못했다. 실측에서 reason=DB_HIT_CACHE_KEY_MISMATCH_SYNCED로 키가 다른 행이 그대로 수용됐고, claude 요청에 ollama 산출물이 나갈 수 있었다. - 조회 조건으로 쓰려면 repoUrlNorm/commitSha와 같은 층위가 필요해 AnalysisCache에 llm_provider를 비정규화했다. - COALESCE(c.llmProvider, :default)로 비교한다. 이 컬럼 이전에 쌓인 행을 죽은 캐시로 만들면 전면 재분석이 걸리는데, 그 시점까지 기본값 외의 제공자로 만들어진 캐시는 존재할 수 없으므로 기본 제공자 산출물로 간주해 계속 재사용한다. - FAILED 쿨다운 조회는 제공자를 가리지 않는 기존 경로를 그대로 둔다. 쿨다운은 "같은 repo/commit이 방금 실패했으니 잠시 쉬자"는 보호 장치이고, 실패는 대개 구조 단계에서 나서 제공자와 무관하다. 검증: 실제 JPQL을 H2에 태워 ollama 캐시가 claude 요청에 안 잡히고, provider가 null인 과거 행은 기본 제공자 요청에만 잡히는 것을 확인했다.
LLM 단계를 기본 활성화했지만 compose에는 모델 서버가 없었다. 컨테이너 안에서 ollama.base-url 기본값(localhost:11434)은 앱 자신을 가리키므로, 그대로 배포하면 모든 run이 LLM 실패로 PARTIAL_SUCCESS가 되고 READY 캐시도 발행되지 않는다. - 포트를 호스트에 노출하지 않는다. 앱은 compose 네트워크에서 http://ollama:11434로 닿고, 개발 머신에는 native ollama가 11434를 쓰는 경우가 많아 노출하면 바인드가 충돌한다. 외부에서 붙어야 하면 OLLAMA_PUBLISH_PORT와 함께 ports 주석을 푼다. - ollama-model-init은 모델을 한 번 받아두는 1회성 서비스다. ollama 이미지는 모델을 자동으로 받지 않아서, 이게 없으면 앱이 먼저 뜨고 첫 run의 LLM 단계가 model not found로 실패한다. app이 완료를 기다리게 해 그 창을 없앤다. 이미 받아둔 모델이면 매니페스트만 확인하고 끝난다. - 모델은 볼륨에 보관한다. 없으면 컨테이너를 다시 만들 때마다 6.6GB를 다시 받는다. 주의: qwen3.5:9b는 num-ctx 32768 기준 약 7.2GB를 쓴다. 앱과 합쳐 서버 여유 메모리를 확인해야 한다.
Feat/#84 Add Ollama LLM Model
Exclude all markdown files except README.md from tracking.
Updated README.md to provide detailed project information, analysis pipeline, core analysis methods, and environment setup instructions.
Document third-party AI components and their licenses.
Document third-party licenses and dependencies used in Oh! SS Backend.
Clarify licensing information and reference third-party licenses.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
주요 반영 내용
Repository 분석
AI 분석
문서 및 Open Source 정리
🚀 Release
본 PR Merge 후
main브랜치를 공개SW 개발자대회 최종 제출 기준 버전으로 사용합니다.Merge 이후에는 치명적인 오류 수정 외 신규 기능 추가를 지양하고, 최종 제출 버전을 기준으로 Release / Tag를 생성합니다.