Skip to content

Repository files navigation

CoFix AI

CoFix AI는 BE가 protobuf/gRPC로 전달한 학습 데이터를 Gemini에 전달하고, 세 가지 기능의 결과를 다시 protobuf로 반환한다.

  • 코드 분석: 현재 코드의 오류와 patch로 해결된 분류를 반환
  • 문제 생성: 최근 이슈를 우선순위화해 4지선다 문제를 반환
  • 학습 챗봇: 누적 약점과 최근 이슈를 참고한 Markdown 답변을 반환

실행 및 protobuf

git clone --recurse-submodules https://github.com/Yanus306/CoFix-AI.git
cd CoFix-AI
.\.venv\Scripts\python.exe scripts\generate_protos.py
.\.venv\Scripts\python.exe -m unittest discover

protoYanus306/Cofix-proto 서브모듈이며 .proto 파일만 둔다. 생성된 Python binding은 generated_proto에 둔다. gRPC 서버의 기본 주소는 127.0.0.1:50051이다.

1. 코드 분석

구현 파일: ai_feedback_pipeline.py

입력 protobuf

AnalyzeCodeRequest는 다음 세 필드를 받는다. reserved 1은 과거에 사용했던 필드 번호를 재사용하지 않기 위한 예약 번호다.

message RecentIssue {
  string dataset = 1;
  string title = 2;
  repeated string learning_directions = 3;
  string code = 4;
  string guide = 5;
}

message LearningContext {
  repeated RecentIssue recent_issues = 1;
}

message AnalyzeCodeRequest {
  reserved 1;
  string code = 2;
  string patch = 3;
  LearningContext learning_context = 4;
}
  • code: 분석할 최종 전체 코드
  • patch: 작성 중 변경된 표준 unified diff 원문. 오류 판정의 기준은 code이고 patch는 수정·개선 여부를 확인하는 보조 자료다.
  • learning_context.recent_issues: 최근 분석 이슈. 현재 코드에 실제로 존재하지 않는 과거 오류를 새 이슈로 만들지는 않는다.

실제 전달 입력 (textproto)

code: "def calculate_average(values):\n    total = 0\n    for i in range(len(values)):\n        total += values[i]\n    return total / len(values)"
patch: "===================================================================\n--- a/calculate.py\n+++ b/calculate.py\n@@ -3 +3 @@\n-    for i in range(len(values) + 1):\n+    for i in range(len(values)):"
learning_context {
  recent_issues {
    dataset: "edge_case"
    title: "빈 리스트 평균 계산 오류"
    learning_directions: "빈 입력 검사"
    learning_directions: "경계값 처리"
    code: "return total / len(values)"
    guide: "🚨 ## 문제\n빈 리스트에서 0으로 나눌 수 있음\n💡 ## 해결\n빈 입력을 먼저 검사\n✨ ## 핵심 원리\n연산 전에 경계값을 확인해야 함"
  }
  recent_issues {
    dataset: "loop_control"
    title: "반복 범위가 리스트 길이를 초과함"
    learning_directions: "반복문 범위"
    learning_directions: "오프바이원 오류"
    code: "for i in range(len(values) + 1):\n    total += values[i]"
    guide: "🚨 ## 문제\n유효 인덱스를 초과함\n💡 ## 해결\n올바른 반복 범위 사용\n✨ ## 핵심 원리\n유효 인덱스는 0부터 길이-1까지임"
  }
}

출력 protobuf

message CodeIssue {
  string code = 1;
  string label = 2;
  string title = 3;
  string description = 4;
  repeated string learning_directions = 5;
  string dataset = 6;
  string guide = 7;
}

message AnalyzeCodeResponse {
  repeated CodeIssue issues = 1;
  reserved 2;
  repeated string improvements = 3;
}

issues의 각 항목은 오류 코드 범위, 분류 표시명, 제목, 설명, 학습 방향, dataset key, 가이드의 7개 필드를 가진다. guide는 항상 다음 섹션 헤더로 정규화된다.

🚨 ## 문제
문제 내용
💡 ## 해결
해결 내용
✨ ## 핵심 원리
핵심 원리

improvements는 patch로 해결되었다고 판단한 분류의 dataset key만 중복 없이 담는다. 따라서 CodeImprovement 객체나 코드·제목을 넣지 않는다.

실제 출력 예시

위 입력을 현재 코드로 protobuf에 매핑한 전체 출력은 다음과 같다.

issues {
  code: "def calculate_average(values):\n    total = 0\n    for i in range(len(values)):\n        total += values[i]\n    return total / len(values)"
  label: "경계값"
  title: "빈 리스트에서 평균을 계산할 수 없음"
  description: "values가 비어 있으면 len(values)가 0이어서 ZeroDivisionError가 발생합니다."
  learning_directions: "빈 입력 검사"
  learning_directions: "경계값 처리"
  dataset: "edge_case"
  guide: "🚨 ## 문제\n빈 리스트에서 0으로 나눔\n💡 ## 해결\n계산 전에 빈 입력을 처리함\n✨ ## 핵심 원리\n연산 전 경계값을 검사해야 함"
}
improvements: "loop_control"

최종 코드가 다음처럼 두 오류를 모두 수정된 상태로 실제 Gemini 분석에 전달된 경우 정규화 결과는 다음과 같다.

{
  "issues": [],
  "improvements": [
    "loop_control",
    "edge_case"
  ]
}

처리 순서는 입력 검증 → 분류표 로딩 → 코드 줄 번호화(프롬프트 내부) → patch와 학습 문맥을 포함한 Gemini 요청 → JSON 파싱 → dataset/label/7개 필드 검증 → guide 정규화 → protobuf 매핑이다.

2. 문제 생성

구현 파일: issue_quiz.py

입력 protobuf

message RecentIssue {
  string dataset = 1;
  string title = 2;
  repeated string learning_directions = 3;
  string code = 4;
  string guide = 5;
}

message GenerateIssueQuizRequest {
  string selected_level = 1;
  int32 problem_count = 2;
  repeated RecentIssue issues = 3;
}
  • selected_level: easy, medium, hard 중 하나
  • problem_count: 양의 문제 수
  • issues: 코드 분석에서 정리된 개별 이슈 목록. 현재 기본값은 3개지만 요청값을 검증해 처리한다.

실제 전달 입력 (textproto)

selected_level: "medium"
problem_count: 3
issues {
  dataset: "loop_control"
  title: "반복 범위가 리스트 길이를 초과함"
  learning_directions: "반복문 범위"
  learning_directions: "오프바이원 오류"
  code: "for i in range(len(values) + 1):\n    total += values[i]"
  guide: "🚨 ## 문제\n유효 인덱스를 초과함\n💡 ## 해결\n올바른 반복 범위 사용\n✨ ## 핵심 원리\n유효 인덱스는 0부터 길이-1까지임"
}
issues {
  dataset: "edge_case"
  title: "빈 리스트 평균 계산 오류"
  learning_directions: "빈 입력 검사"
  learning_directions: "경계값 처리"
  code: "return total / len(values)"
  guide: "🚨 ## 문제\n빈 리스트에서 0으로 나눌 수 있음\n💡 ## 해결\n빈 입력을 먼저 검사\n✨ ## 핵심 원리\n연산 전에 경계값을 확인해야 함"
}

출력 protobuf 및 실제 출력

message QuizChoice {
  string id = 1;
  string text = 2;
}

message QuizCodeBlock {
  string language = 1;
  string content = 2;
}

message QuizProblem {
  string question = 1;
  optional QuizCodeBlock code_block = 2;
  repeated QuizChoice choices = 3;
  string answer = 4;
  string explanation = 5;
}

message GenerateIssueQuizResponse {
  repeated QuizProblem problems = 1;
}

실제 protobuf 응답은 다음처럼 각 문제에 질문, 선택적 코드 빈칸, A~D 선택지, 정답, 해설을 담는다.

problems {
  question: "다음 반복문이 IndexError를 발생시키지 않도록 빈칸에 들어갈 표현은?"
  code_block { language: "python" content: "for i in range(/* 빈칸 */):\n    total += values[i]" }
  choices { id: "A" text: "len(values)" }
  choices { id: "B" text: "len(values) + 1" }
  choices { id: "C" text: "len(values) - 1" }
  choices { id: "D" text: "values" }
  answer: "A"
  explanation: "range(len(values))는 0부터 len(values)-1까지 순회합니다."
}
problems {
  question: "빈 리스트의 평균을 계산할 때 가장 먼저 확인해야 하는 조건은?"
  choices { id: "A" text: "합계가 음수인지" }
  choices { id: "B" text: "리스트가 비었는지" }
  choices { id: "C" text: "값이 정렬됐는지" }
  choices { id: "D" text: "중복 값이 있는지" }
  answer: "B"
  explanation: "빈 리스트는 길이가 0이므로 나눗셈 전에 빈 입력을 처리해야 합니다."
}
problems {
  question: "리스트 인덱스의 올바른 유효 범위는?"
  choices { id: "A" text: "1부터 길이까지" }
  choices { id: "B" text: "0부터 길이까지" }
  choices { id: "C" text: "0부터 길이-1까지" }
  choices { id: "D" text: "1부터 길이-1까지" }
  answer: "C"
  explanation: "길이가 n인 리스트의 유효 인덱스는 0부터 n-1까지입니다."
}

처리 순서는 난이도·문제 수·이슈 필드 검증 → dataset 반복 횟수와 보안/런타임 중요도를 반영한 우선순위화 → 선택한 난이도와 사용자 약점에 맞춘 주제 계획 → Gemini 요청 → 문제 수, A~D, 정답, 선택적 코드 블록 검증 → protobuf 매핑이다.

3. 학습 챗봇

구현 파일: learning_chatbot.py

입력 protobuf

message CategoryCount {
  string dataset = 1;
  int32 count = 2;
}

message RecentIssue {
  string dataset = 1;
  string title = 2;
  repeated string learning_directions = 3;
  string code = 4;
  string guide = 5;
}

message ChatRequest {
  reserved 1;
  string title = 2;
  string message = 3;
  repeated CategoryCount category_counts = 4;
  repeated RecentIssue recent_issues = 5;
  string summary = 6;
}

title은 현재 채팅방 제목(비어 있으면 모델이 새 제목을 제안), message는 사용자 질문, category_counts는 분류별 누적 횟수, recent_issues는 최근 이슈, summary는 BE가 보관한 이전 대화 요약 문자열이다. 0회 분류는 모델 프롬프트에 넣기 전에 제거하고, 최근 이슈와 요약은 각각 최대 5개 범위로 압축한다.

실제 전달 입력 (textproto)

title: "반복문 범위 오류"
message: "최근 반복문 실수를 참고해서 이 코드를 어떻게 고칠지 알려줘."
category_counts { dataset: "syntax_structure" count: 0 }
category_counts { dataset: "variable_type" count: 2 }
category_counts { dataset: "scope_lifetime" count: 0 }
category_counts { dataset: "operator_logic" count: 1 }
category_counts { dataset: "assignment_mutability" count: 0 }
category_counts { dataset: "type_annotation" count: 0 }
category_counts { dataset: "string_handling" count: 0 }
category_counts { dataset: "array_collection" count: 0 }
category_counts { dataset: "data_format_parsing" count: 0 }
category_counts { dataset: "null_missing_value" count: 0 }
category_counts { dataset: "data_validation" count: 1 }
category_counts { dataset: "function_usage" count: 0 }
category_counts { dataset: "api_misuse" count: 0 }
category_counts { dataset: "side_effect" count: 0 }
category_counts { dataset: "conditional" count: 0 }
category_counts { dataset: "loop_control" count: 7 }
category_counts { dataset: "edge_case" count: 4 }
category_counts { dataset: "state_management" count: 0 }
category_counts { dataset: "exception_handling" count: 0 }
category_counts { dataset: "error_propagation" count: 0 }
category_counts { dataset: "logging_diagnostics" count: 0 }
category_counts { dataset: "time_complexity" count: 0 }
category_counts { dataset: "space_complexity" count: 0 }
category_counts { dataset: "algo_selection" count: 0 }
category_counts { dataset: "data_structure_choice" count: 0 }
category_counts { dataset: "recursion" count: 0 }
category_counts { dataset: "memory_management" count: 0 }
category_counts { dataset: "resource_management" count: 0 }
category_counts { dataset: "buffer_boundary" count: 0 }
category_counts { dataset: "ui_dom_rendering" count: 0 }
category_counts { dataset: "tensor_matrix_shape" count: 0 }
category_counts { dataset: "concurrency" count: 0 }
category_counts { dataset: "async_handling" count: 0 }
category_counts { dataset: "transaction_atomicity" count: 0 }
category_counts { dataset: "security_input" count: 0 }
category_counts { dataset: "auth_access_control" count: 0 }
category_counts { dataset: "secret_handling" count: 0 }
category_counts { dataset: "crypto_randomness" count: 0 }
category_counts { dataset: "dependency_config" count: 0 }
category_counts { dataset: "io_network" count: 0 }
category_counts { dataset: "database_query" count: 0 }
category_counts { dataset: "test_coverage" count: 0 }
category_counts { dataset: "readability" count: 0 }
category_counts { dataset: "clean_code" count: 0 }
category_counts { dataset: "maintainability_design" count: 0 }
category_counts { dataset: "performance_runtime" count: 0 }
recent_issues {
  dataset: "loop_control"
  title: "반복 범위가 리스트 길이를 초과함"
  learning_directions: "반복문 범위"
  learning_directions: "오프바이원 오류"
  code: "for i in range(len(values) + 1):\n    total += values[i]"
  guide: "🚨 ## 문제\n유효 인덱스를 초과함\n💡 ## 해결\n올바른 반복 범위 사용\n✨ ## 핵심 원리\n유효 인덱스는 0부터 길이-1까지임"
}
summary: "사용자는 이전 대화에서 반복문 범위 오류의 원인과 오프바이원 개념을 질문했다."

모델 원문과 출력 protobuf

Gemini 원문은 첫 줄의 한 줄 JSON과 두 번째 줄부터의 Markdown으로 구성된다. 서버는 이를 한 번 파싱해 protobuf의 세 필드로 나눈다. Markdown을 JSON 문자열로 변환했다가 다시 복원하는 방식은 사용하지 않는다.

message ChatResponse {
  optional string title = 1;
  optional string conversation_summary = 2;
  string markdown_answer = 3;
}

실제 출력 protobuf 예시는 다음과 같다.

title: "반복문 범위 오류"
conversation_summary: "사용자는 반복 범위 초과 오류의 수정 방향을 질문했고 유효 인덱스와 반복 범위에 관한 힌트를 안내받았다."
markdown_answer: "## 수정 방향\n\n반복 범위를 리스트의 유효 인덱스에 맞춰 확인해 보세요."

처리 순서는 필드와 문자열 검증 → 분류표 key 검증 → 0회 분류 제거 및 오류 횟수 내림차순 정렬 → 최근 이슈/대화 요약의 최대 5개 압축 → 챗봇 프롬프트 생성 → Gemini 원문 응답의 첫 줄 JSON 파싱 → title·conversation_summary·Markdown 분리 → protobuf 응답이다. 코딩과 무관한 질문은 두 메타데이터를 null로 하고 Markdown으로 코딩 질문만 지원한다고 안내한다.

4. gRPC 서버 공통 동작

구현 파일: cofix_ai_server.py

서버는 protobuf 요청을 Python 매핑으로 변환하고 해당 기능을 호출한 뒤 결과를 출력 protobuf로 변환한다.

RPC Python 처리기 결과
CodeAnalysisService.AnalyzeCode ai_feedback_pipeline issues, improvements
IssueQuizService.GenerateIssueQuiz issue_quiz problems
LearningChatbotService.Chat learning_chatbot title, conversation_summary, markdown_answer

입력 오류는 INVALID_ARGUMENT, Gemini 연결 실패는 UNAVAILABLE, 시간 초과는 DEADLINE_EXCEEDED, 예상하지 못한 오류는 INTERNAL gRPC 상태로 변환한다.

완전한 wire 예시는 examples/proto에 있으며, schema 변경 후에는 다음 명령으로 binding을 재생성한다.

.\.venv\Scripts\python.exe scripts\generate_protos.py

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages