본문으로 건너뛰기
5분 AI.
GITHUB TRENDING GO MONTHLY #8 딥다이브 · 2026-10-23 분석

WeKnora 딥다이브
— 문서가 근거 있는 답변과 지식 위키가 되기까지

지식 플랫폼

빠른 판단

무엇
문서를 추출·분할·색인해 근거 기반 RAG·도구 에이전트·연결 위키로 활용하는 Go 지식 플랫폼
이럴 때
사내 보고서·매뉴얼을 자체 호스팅하며 문서 질의응답과 에이전트·위키를 같은 지식 기반으로 운영할 때
입력
문서·보고서·매뉴얼과 사용자 질문
결과
텍스트·이미지 색인, 문서 근거가 연결된 답변과 위키
PDF와 사내 문서를 넣으면 필요한 대목을 찾아 답하고, 도구를 쓰는 에이전트와 연결된 위키로 확장하는 지식 플랫폼입니다. Go 서버·Vue 화면·Python 문서 파서가 어떻게 협력하는지, 파일 하나의 생애를 따라 배웁니다. Tencent/WeKnora · ⭐35,175 · 포크 4,724 · 주 언어 Go · 최신 릴리스 v0.8.3 · 2026-10-23 GitHub 기준.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

문서를 저장하는 데서 끝나지 않고, 답변에 쓸 근거로 가공하는 서비스

WeKnora는 문서를 검색 가능한 지식으로 바꾸고, 그 지식을 RAG·Agent·Wiki 세 방식으로 사용하는 플랫폼입니다. 보고서와 매뉴얼을 읽어 텍스트·이미지를 추출하고, 작은 조각으로 나눠 색인합니다. 사용자가 질문하면 관련 조각을 찾아 언어 모델에 전달하고, 답변에 문서 근거를 연결합니다.

이 저장소에서 배울 핵심은 모델을 만드는 법보다, 모델이 믿을 만한 자료를 찾고 사용하는 제품을 만드는 법입니다. 검색·비동기 작업·권한·화면 갱신·배포가 모두 이 목적을 위해 연결됩니다.

용어 먼저
RAG — 검색 증강 생성
질문에 답하기 전에 자료를 검색하고, 찾은 내용을 답변의 참고 자료로 주는 방식입니다. 문서를 올릴 때마다 모델의 가중치를 다시 학습시키는 방식과 다릅니다. 자료를 잘못 찾으면 답변도 틀릴 수 있습니다.
한 문장 비유

파일 창고에, 자료를 찾아 주는 사서와 문서를 정리하는 편집자가 함께 들어옵니다

RAG는 질문에 맞는 책과 페이지를 찾아 주는 사서입니다. Agent는 검색만으로 해결되지 않으면 다른 도구도 쓰는 조사 담당자입니다.

Wiki는 여러 문서의 개념을 연결해 읽기 좋은 지식 문서를 만드는 편집자입니다. 세 역할은 원문이라는 같은 재료를 사용하지만, 실행 흐름과 결과물은 다릅니다.

사용 방식사용자 질문 / 요청소스에서 찾아볼 입구
지식 질의응답“이 매뉴얼의 환불 조건은?” — 근거를 검색해 답변 internal/application/service/session_knowledge_qa.go
에이전트“관련 자료를 더 읽고 비교해 줘” — 모델 판단에 따라 도구 호출 반복 internal/agent/engine.go
지식 위키문서에서 개념을 추출하고 서로 연결된 페이지로 정리 internal/application/service/wiki_ingest_batch.go

먼저 구분할 두 시간대

자료를 넣는 시간
문서 업로드 → 원문 저장 → 파싱 → 청크 분할 → 임베딩·색인 → 검색 준비

질문에 답하는 시간
질문 → 관련 청크 검색 → 순서 재평가 → 문맥 구성 → 모델 답변 → 출처 표시

앞의 경로는 오래 걸려도 재시도할 수 있어야 하고, 뒤의 경로는 사용자가 기다리므로 진행 상황을 빨리 보여줘야 합니다. 이 차이가 작업 큐와 스트리밍 화면을 따로 설계하는 이유입니다. 파일을 받았다는 응답만으로 “이제 검색할 수 있다”고 판단하면 안 됩니다.

읽는 순서
처음에는 파일 하나와 질문 하나만 따라가세요

처음부터 모든 모델 공급자와 저장소 구현을 읽을 필요는 없습니다. 업로드 함수 → 문서 처리 함수 → 질의응답 파이프라인을 한 번 연결하면, 나머지는 어떤 부품을 교체하는 코드인지 보이기 시작합니다.

2왜 주목받는가

순위 자체보다, 하나의 저장소에 모인 문제 해결 범위에 주목하기

2026-10-23 분석 대상은 GitHub Trending Go Monthly #8입니다. 스타 35,175개와 포크 4,724개는 관심의 크기를 보여 주지만, 정확도나 운영 안정성을 증명하는 수치는 아닙니다. 다음은 기능과 코드에서 읽어 낸 주목할 이유이며, 순위 상승의 인과관계를 입증한 분석은 아닙니다.

2-1. RAG 데모를 넘어 “계속 쓰는 지식 서비스”를 다룬다

단순한 PDF 채팅 예제는 문서를 읽고 답하는 순간에 집중합니다. WeKnora에는 문서 교체·재파싱·폴더·외부 데이터 소스·공간 권한·위키 변경 이력까지 이어지는 코드가 있습니다. internal/application/service/knowledge_create.go의 중복 검사와 internal/application/service/knowledge_process.go의 처리 상태만 읽어도, “한 번 성공하는 기능”을 “반복해서 쓰는 기능”으로 만드는 차이가 드러납니다.

2-2. 최근 릴리스의 초점이 근거와 멀티모달로 넓어진다

v0.8.3(2026-10-14)의 README 릴리스 설명은 이미지 갤러리, 텍스트·이미지를 함께 다루는 임베딩, 이미지 인식 재정렬, 위키 그래프, 원문 인용 위치 이동을 강조합니다. 기능 소개와 별개로 코드에서는 internal/application/service/chat_pipeline/rerank.go가 이미지 로더를 재정렬 옵션에 연결하고, internal/sourceloc/가 원문 위치를 다루는 것을 확인할 수 있습니다. 각 모델과 파일 형식에서 같은 품질이 나온다는 의미는 아닙니다.

2-3. 선택지를 바꾸면서도 공통 흐름을 유지한다

표준 구성은 PostgreSQL 계열 저장소와 Redis를 사용하고, Lite는 SQLite와 메모리 기반 실행을 사용합니다. 파서는 Go 내부·Python 서비스·원격 API로 나뉘어도 types.ReadResult로 결과를 모읍니다. 부품을 갈아 끼워도 문서 처리와 질의응답의 중심 흐름이 유지되는 구조가 이 저장소의 중요한 학습 가치입니다.

2-4. 비슷한 프로젝트와 무엇을 비교해야 할까

아래는 공식 프로젝트 설명과 WeKnora의 코드 범위를 바탕으로 한 학습 관점의 비교입니다. 기능이 서로 겹치므로 “어느 제품에 이 기능이 전혀 없다”는 표로 읽으면 안 됩니다.

대상공식 설명의 중심WeKnora에서 특히 볼 지점
Dify 시각적 워크플로, RAG, Agent를 결합한 LLM 앱 개발 플랫폼 지식 문서의 입력·검색·위키화를 Go 서비스 계층에서 이어 읽기 좋습니다.
RAGFlow 복잡한 문서 이해와 템플릿 기반 분할을 강조하는 RAG 엔진 파서 경계뿐 아니라 Agent 루프·공간 권한·Lite 실행을 함께 학습할 수 있습니다.
LangChain 모델·검색·도구를 조립해 앱과 Agent를 만드는 프레임워크 이미 연결된 UI·계정·문서 상태·배포 설정을 보며 완성된 서비스의 연결 비용을 배웁니다.
직접 만든 RAG 예제필요한 기능만 빠르게 이해하고 실험하기 좋음 실패 복구·동시성·데이터 경계가 추가되는 과정을 비교할 수 있습니다.
선택 기준 비유

프레임워크는 부품 상자에 가깝고, WeKnora는 부품이 연결된 작업실에 가깝습니다. 자유롭게 새 구조를 설계할 때와, 이미 돌아가는 서비스의 연결부를 배울 때 필요한 재료가 다릅니다.

장점에는 비용이 따릅니다. 표준 배포에는 여러 서비스가 필요하고, 설정과 소스 규모도 큽니다. 문서 질의응답만 필요하다면 작은 예제가 학습의 출발점으로 더 쉬울 수 있습니다. WeKnora의 강점은 모든 상황의 최저 비용보다 지식 서비스의 여러 층을 한 번에 추적할 수 있다는 점입니다.

3기술 스택 전체 지도

언어 이름보다 각 기술이 책임지는 경계를 함께 보기

버전은 커밋 314a6fa의 의존성 파일 기준입니다. Go 모듈의 버전, npm의 허용 범위, Python의 최소 버전은 서로 다른 의미입니다. 특히 ^와 >=가 붙은 선언을 실제 설치 버전처럼 읽지 마세요.

3-1. 백엔드: 요청을 받고, 지식을 가공하고, 실행 흐름을 통제한다

기술 / 실제 선언역할확인 파일
Go 1.26.0HTTP API, 문서 처리 조정, 검색, Agent 실행의 주 언어go.mod
Gin v1.12.0라우팅과 HTTP 미들웨어go.mod, internal/router/router.go
GORM v1.31.1문서·세션·공간 등 관계형 데이터 접근go.mod, internal/application/repository/
Dig v1.19.0생성자와 인터페이스를 연결하는 의존성 주입internal/container/container.go
Asynq v0.26.0Redis 기반 문서 처리·후처리 작업 큐go.mod, internal/router/
go-redis v9.14.1큐 주변 상태·스트림·분산 실행 조정go.mod
gRPC v1.81.0
Protobuf v1.36.11
Go 앱과 문서 파서 사이의 타입이 정해진 통신docreader/proto/docreader.proto
pgvector-go v0.3.0
Qdrant client v1.18.1
벡터 저장소 연결의 일부; 이 버전은 서버 버전이 아닌 클라이언트 버전go.mod
mcp-go v1.1.1
Ollama v0.23.2
MCP와 로컬 모델 서비스 연동go.mod
OpenTelemetry v1.43.0실행 구간 추적; Langfuse 연동 코드와 함께 읽기internal/tracing/langfuse/

3-2. 프론트엔드: 긴 작업을 사용자가 이해할 수 있게 보여 준다

패키지frontend/package.json잠금 버전 / 역할
Vue^3.5.343.5.34 — 화면 컴포넌트와 반응형 상태
TypeScript~6.0.36.0.3 — 화면 데이터의 타입
Vite^7.3.57.3.6 — 개발·정적 자산 빌드
Pinia^3.0.43.0.4 — 화면 간 공유 상태
TDesign Vue Next^1.19.21.19.2 — UI 컴포넌트
Axios^1.16.01.16.0 — 일반 HTTP API 호출
fetch-event-source^2.0.12.0.1 — 답변 이벤트 스트림 수신

잠금 버전은 frontend/package-lock.json에서 확인할 수 있습니다. 문서 미리보기를 위한 pdfjs-dist ^6.3.289, docx-preview ^0.3.7와, Markdown을 위한 marked ^17.0.5, HTML 정제를 위한 dompurify ^3.4.11도 선언되어 있습니다. 의존성이 있다는 사실만으로 모든 렌더링 경로가 안전하다고 단정할 수는 없습니다.

3-3. 문서 파싱: Go 앱 바깥의 무거운 도구를 감싼다

docreader/pyproject.toml은 Python >=3.10.18을 요구합니다. grpcio >=1.78.0, markitdown[docx,pdf,xls,xlsx,pptx] >=0.1.3, opendataloader-pdf >=2.4.7, rapidocr >=3.0, playwright >=1.55.0, pypdf >=6.13.3 등이 선언되어 있습니다. 이는 최소 요구 버전이며, docreader/uv.lock에는 선택된 의존성 버전이 기록되어 있습니다.

모든 문서가 Python으로 가는 것은 아닙니다. internal/infrastructure/docparser/engine_registry.go의 NewReader는 단순 형식을 Go에서 처리할 수 있고, anydoc 엔진은 빌드 태그와 네이티브 라이브러리가 갖춰졌을 때 프로세스 안에서 동작합니다. go.mod에는 third_party/anydoc-go를 가리키는 로컬 교체 설정이 있습니다.

3-4. 인프라·빌드: 개발 도구 버전과 실행 이미지 버전은 다르다

구성실제 값읽어야 할 의미
기본 검색 DBparadedb/paradedb:v0.25.11-pg17표준 Compose의 PostgreSQL 17 계열 이미지
기본 큐redis:7.0-alpine기본 구성의 Redis 이미지 태그
선택 벡터 DBqdrant/qdrant:v1.16.2
milvusdb/milvus:v2.6.11
선택 profile; 모두 기본으로 켜지는 것은 아님
선택 그래프 DBneo4j:2025.10.1그래프 기능용 선택 서비스
앱 이미지golang:1.26-bookworm → debian:12.12-slim빌드 도구와 실행 환경 분리; BrowserSkill 빌드 단계도 있음
UI 이미지Node 24 → Nginx 1.30.3-alpineUI 빌드 산출물과 Nginx 설정·시작 스크립트를 실행 이미지에 복사; 기반 이미지 digest 고정
문서 파서 이미지python:3.10.18-bookwormuv 잠금 설치, LibreOffice·Java·Playwright 등 포함
Helmchart 0.1.0, app v0.8.3helm/Chart.yaml의 Kubernetes 조건은 >=1.25.0-0

표의 이미지 값은 docker-compose.yml, 빌드 단계는 docker/Dockerfile.app, docker/Dockerfile.docreader, frontend/Dockerfile 기준입니다. 앱 Dockerfile의 WITH_ANYDOC=1 기본값은 Rust 도구 체인과 CGO까지 빌드 과정에 들어오는 이유를 설명합니다. “Go 프로젝트니까 실행 파일만 빌드하면 된다”는 예상과 실제 배포 구성은 다릅니다.

의존성 파일 읽기
허용 범위와 잠금 파일
frontend/package.json은 어떤 버전을 허용할지, frontend/package-lock.json은 어떤 버전을 선택했는지 보여 줍니다. Python도 docreader/pyproject.toml의 하한과 docreader/uv.lock을 구분해야 합니다. 재현 가능한 빌드를 이해하려면 두 종류의 파일을 함께 읽으세요.

3-5. CI는 어떤 품질을 확인하도록 설계되어 있나

.github/workflows/app.yml은 Go 1.26 환경에서 형식 검사·go vet·go test·서버 빌드를 정의합니다. .github/workflows/frontend.yml은 Node 24에서 테스트·타입 검사·빌드를 수행하도록 구성되어 있습니다. .github/workflows/docreader.yml은 Python 테스트와 Go 클라이언트를, .github/workflows/anydoc.yml은 Rust 엔진 경로를 별도로 다룹니다.

여러 언어를 쓰는 서비스의 품질 경계는 저장소 하나가 아니라, 각 언어의 검사와 서로 만나는 인터페이스에 놓입니다. CI 파일에 검사가 정의되어 있다는 사실과 해당 커밋에서 모든 검사가 통과했다는 사실은 구분해야 합니다.

4아키텍처 심화 분석

전체 지도를 그린 뒤, 네 가지 설계 패턴으로 연결부를 읽기

4-1. 전체 시스템: 표준 배포를 기준으로 보기

[사용자 브라우저: Vue + Pinia] | 업로드 / 질문 ^ 답변·진행 이벤트(SSE) v | [frontend: Nginx :80 / API·MCP 프록시] | v [app: Go + Gin :8080] | 인증·공간 권한 → Handler → Service → Repository | +-- 문서 입력 → 파일 저장 + 문서 메타데이터 저장 | | | v | [Redis + Asynq 작업 큐] | | 앱의 작업 처리기 | v | 파서 선택 → [Go 내부 / anydoc] | +--→ [Python docreader :50051] | +--→ [선택한 원격 파싱 API] | | | 청크 분할 → 임베딩 → 검색 색인 | +--→ [선택: Wiki 생성·갱신] | +-- 질문 → [RAG 이벤트 파이프라인] → 답변·출처 +-- 요청 → [Agent 반복 루프] → 검색 / MCP / 기타 도구 | +--→ [기본: ParadeDB / PostgreSQL] 메타데이터·검색 +--→ [기본: 로컬 파일 저장소] 원문·이미지 +--→ [모델 서비스] 채팅 / 임베딩 / 재정렬 / 이미지 설명 +--→ [선택: 외부 벡터 DB / Neo4j / 객체 저장소 / 추적]

이 그림의 상자는 모두 별도 마이크로서비스를 뜻하지 않습니다. Handler·Service·Repository와 기본 작업 처리기는 Go 앱 내부의 역할 구분입니다. Redis 큐가 있다고 작업 처리기가 반드시 별도 배포 단위가 되는 것은 아닙니다. 모델 서버는 배포 선택에 따라 로컬일 수도 외부 API일 수도 있습니다.

Lite에서는 핵심 저장소가 SQLite로 바뀌고 Redis 없는 작업 실행 및 메모리 스트림을 사용합니다. 이를 “표준 배포의 모든 기능이 한 실행 파일로 동일하게 압축된다”는 뜻으로 해석하면 안 됩니다. docs/LITE.md는 공유 공간과 기본 파서 범위의 차이도 명시합니다.

4-2. 패턴 ① 계층 분리 + 의존성 주입 + 교체 가능한 어댑터

cmd/server/main.go의 main은 container.BuildContainer(runtime.GetContainer())로 부품을 연결합니다. internal/container/container.go의 BuildContainer는 DB·파일 서비스·검색 엔진·저장소·서비스·핸들러 생성자를 Dig에 등록합니다.

설계 용어
의존성 주입과 어댑터
서비스가 필요한 부품을 직접 만들지 않고 생성자의 인자로 받는 것이 의존성 주입입니다. 어댑터는 외부 도구의 서로 다른 사용법을 내부의 같은 인터페이스로 바꾸는 연결 부품입니다. 테스트에서는 실제 서버 대신 작은 가짜 구현을 넣을 수 있습니다.
// internal/types/interfaces/retriever.go의 실제 인터페이스 발췌
type RetrieveEngine interface {
    EngineType() types.RetrieverEngineType
    Retrieve(ctx context.Context, params types.RetrieveParams) ([]*types.RetrieveResult, error)
    Support() []types.RetrieverType
}

상위 코드는 PostgreSQL의 질의 문법이나 Qdrant 호출 형식을 모두 알 필요가 없습니다. Retrieve와 Support 같은 공통 약속을 사용하고, 구체적인 연결 차이는 internal/application/repository/retriever/가 맡습니다. 새 엔진을 추가할 때도 지원 검색 방식과 오류 계약을 맞춰야 한다는 뜻입니다.

문서 파서도 같은 방향입니다. docparser.NewReader는 이름으로 등록된 엔진을 찾고, 엔진을 명시하지 않은 단순 형식의 업로드 파일에는 SimpleFormatReader를 사용합니다. 그 밖의 경로는 연결된 docreader로 갑니다. 등록된 엔진의 초기화 실패는 오류로 반환합니다. 다만 anydoc은 OCR이 필요한 PDF를 연결된 docreader로 넘길 수 있습니다.

학습할 때는 인터페이스 → 생성자 → 등록 위치 → 실제 구현 순서로 읽으세요. 인터페이스만 보면 가능한 기능을, 컨테이너까지 보면 현재 실행 환경에서 어떤 구현이 선택되는지를 알 수 있습니다.

4-3. 패턴 ② 오래 걸리는 문서 처리를 작업으로 분리한다

internal/application/service/knowledge_create.go의 CreateKnowledgeFromFile을 보면 업로드의 책임이 분명해집니다. 확장자·중복·할당량 등을 검사하고 원문을 저장한 뒤, 문서 레코드를 만들고 처리 작업을 넣습니다.

  1. 파일 해시와 공간·지식베이스 정보를 이용해 중복 여부를 확인합니다.
  2. 문서 객체를 ParseStatus: "pending", EnableStatus: "disabled"로 준비합니다.
  3. fileSvc.SaveFile로 파일을 저장한 후 repo.CreateKnowledge를 호출합니다.
  4. types.DocumentProcessPayload에 문서 ID·공간 ID·파일 정보를 담습니다.
  5. asynq.NewTask(types.TypeDocumentProcess, ...)를 만들고 큐에 넣습니다. 이 업로드 경로는 asynq.MaxRetry(3)을 지정합니다.
  6. internal/application/service/knowledge_process.go의 ProcessDocument가 파싱과 분할을 조정하고, processChunks가 청크 저장과 색인을 진행합니다.
실패 지점코드에서 보이는 동작배울 점
원문 저장 실패문서 레코드 생성 전에 오류 반환존재하지 않는 파일을 가리키는 행을 줄입니다.
문서 행 생성 실패이미 저장한 파일을 삭제하려 시도DB 트랜잭션 밖 자원에는 보상 정리가 필요합니다.
작업 직렬화·등록 실패실패 상태를 기록하고 문서 객체를 반환할 수 있음요청 성공과 후속 처리 성공은 서로 다른 상태입니다.
Redis 미설정BuildContainer가 SyncTaskExecutor를 주입이름만 보고 내구성 있는 외부 큐라고 가정하면 안 됩니다.

파일 저장·문서 등록·색인 완료는 서로 다른 성공이며, 서비스는 그 사이의 상태와 실패를 표현해야 합니다. 사용자에게는 “업로드됨”과 “검색 준비 완료”를 구분해 보여 줄 필요가 있습니다.

표준 모드에서는 core·post-process·enrichment·maintenance·shared·wiki 작업 처리 풀을 등록합니다. 큰 문서의 부가 처리 때문에 기본 처리가 모두 밀리지 않도록 용량을 나누는 설계입니다. 실제 처리량은 풀 크기뿐 아니라 파서·모델 API·DB 속도에도 제한됩니다.

같은 문서를 위키로 만드는 후속 경로

internal/application/service/wiki_ingest.go의 EnqueueWikiIngest와 internal/application/service/wiki_ingest_batch.go의 ProcessWikiIngest를 연결해 보세요. mapOneDocument는 문서별 재료를 만들고, reduceSlugUpdates는 같은 페이지 식별자로 모인 갱신을 다룹니다.

이는 문서마다 얻은 내용을 공통 페이지로 합치는 Map/Reduce식 분해로 읽을 수 있습니다. 여러 문서가 같은 위키 페이지를 수정할 수 있으므로 단순한 병렬 실행만으로는 충분하지 않습니다. internal/application/service/wiki_ingest.go에는 Redis 모드의 페이지별 잠금과 지식베이스별 동시 실행 상한, 업로드를 모으는 30 * time.Second 지연이 나타납니다.

4-4. 패턴 ③ 이벤트로 조립하는 RAG 파이프라인

internal/application/service/session_knowledge_qa.go는 검색이 필요한지 판단한 뒤 단계를 조립합니다. 아래는 RAG 경로의 실제 구성 부분입니다. 각 AddIf가 어떤 기능을 선택적으로 켜는지 읽어 보세요.

// internal/application/service/session_knowledge_qa.go 발췌
pipeline = types.NewPipelineBuilder().
    AddIf(hasHistory, types.LOAD_HISTORY).
    Add(types.MEMORY_RECALL).
    Add(types.QUERY_UNDERSTAND).
    Add(types.CHUNK_SEARCH_PARALLEL).
    Add(types.CHUNK_RERANK).
    AddIf(webSearchEnabled, types.WEB_FETCH).
    Add(types.CHUNK_MERGE).
    Add(types.FILTER_TOP_K).
    AddIf(chatManage.DataAnalysisEnabled, types.DATA_ANALYSIS).
    Add(types.INTO_CHAT_MESSAGE).
    Add(types.CHAT_COMPLETION_STREAM).
    Build()

internal/application/service/chat_pipeline/chat_pipeline.go의 Plugin은 ActivationEvents와 OnEvent를 요구합니다. EventManager.Register는 이벤트별 플러그인을 등록하고, buildHandler는 next를 이어 호출하는 체인을 만듭니다. 세션 서비스의 KnowledgeQAByEvent는 조립된 이벤트 목록을 순서대로 실행합니다.

단계실제 코드주의해서 볼 행동
병렬 검색internal/application/service/chat_pipeline/search_parallel.go의 PluginSearchParallel.OnEvent 청크 검색과 엔티티 검색에 각각 ChatManage.Clone()을 주고, 끝난 뒤 결과를 합칩니다.
청크·웹 검색internal/application/service/chat_pipeline/search.go의 PluginSearch.OnEvent 지식베이스와 선택적 웹 검색을 병렬 실행하며, 남은 결과가 있으면 일부 실패를 경고로 다룰 수 있습니다.
재정렬internal/application/service/chat_pipeline/rerank.go의 PluginRerank.OnEvent 모델이 없거나 로드·호출에 실패하면 검색 순서를 살리는 경로가 있습니다. 정상 평가 후 모두 걸러진 경우와 다릅니다.
결과 제한internal/application/service/chat_pipeline/filter_top_k.go의 PluginFilterTopK.OnEvent 병합·재정렬·검색 결과 중 사용할 목록을 고르고, 동점에서도 재현 가능한 순서로 정렬합니다.
비유

RAG는 답변 원고가 지나가는 편집 공정입니다. 자료 수집, 중요도 판단, 중복 정리, 분량 조절을 차례로 거칩니다. 어느 공정이 비었는지 알아야 고칠 수 있습니다. 최종 답변만 보고 검색 실패와 글쓰기 실패를 구분하기는 어렵습니다.

오류 정책은 계층마다 다릅니다. internal/application/service/retriever/composite.go의 CompositeRetrieveEngine.Retrieve는 엔진 수준 오류가 나면 모은 결과를 버리는 반면, 결과 객체가 보고하는 부분 실패는 결과와 오류를 함께 반환합니다. 상위 파이프라인의 “남은 결과 사용” 정책을 모든 저장소 호출에 그대로 적용하면 소스를 잘못 읽게 됩니다.

4-5. 패턴 ④ Agent는 한 번의 답변이 아니라 종료 조건을 가진 반복 실행이다

internal/agent/engine.go의 AgentEngine.Execute는 실행 상태를 만들고, executeLoop와 runReActIteration으로 반복을 진행합니다. 검색 질의응답의 고정 공정과 달리 모델 응답에 따라 다음 도구와 행동이 달라집니다.

// 이해를 위한 의사코드 — 원본 그대로의 Go 코드는 아닙니다.
상태 = 실행 시작 시 전달받은 대화 기록
반복 가능한 동안:
    취소·진행 정체·문맥 크기를 점검
    모델에 현재 상태와 도구 정의 전달
    도구 호출이 있으면 실행하고 결과를 상태에 추가
    최종 답변이 완성되면 종료
    사용자 응답이 필요하면 실행을 중단·보류할 수 있음

withinIterationBudget는 반복 예산을 확인합니다. 음수 MaxIterations는 횟수 제한을 없애지만, 자연 종료·취소·빈 응답·진행 정체 같은 제어는 별개입니다. 기본값을 확인하지 않고 “항상 N번만 돈다”고 설명해서는 안 됩니다.

internal/agent/act.go의 executeToolCalls는 ParallelToolCalls가 켜지고 도구 호출이 둘 이상일 때 병렬 경로를 선택합니다. 병렬 경로에서도 동시 실행을 허용하지 않는 도구는 앞선 호출이 끝난 뒤 순서대로 실행합니다. 응답이 출력 길이 제한으로 잘렸다면 인자가 불완전할 수 있어 도구 호출을 거부합니다. “JSON처럼 보인다”와 “실행해도 되는 완전한 명령이다” 사이의 경계를 보여 주는 사례입니다.

검색 도구의 실제 구현은 internal/agent/tools/search_knowledge.go와 internal/agent/tools/read_document.go에서 볼 수 있습니다. search_knowledge로 후보를 찾고 read_document로 필요한 범위를 읽는 구조는, 모든 문서를 한 번에 모델 문맥에 넣지 않도록 역할을 나눈 예입니다.

엔진은 다음 질문까지의 대화 기록을 자체 캐시에 계속 쌓는 구조가 아닙니다. AgentEngine의 설명대로 호출자가 매 턴 DB에서 기록을 복원해 전달합니다. 한 턴 안의 반복 상태, 턴 사이의 영속 기록, 장기 메모리는 서로 다른 수명입니다.

4-6. 화면과 권한까지 이어서 읽어야 완성된다

frontend/src/api/chat/streame.ts의 useStream은 fetchEventSource로 이벤트를 받고, AbortController와 실행 세대 번호로 이전 요청의 늦은 이벤트를 구분합니다. 이미 접수된 POST를 단순 반복하는 대신, 받은 메시지 ID로 기존 실행에 다시 붙는 조건도 확인할 수 있습니다.

frontend/nginx-api-proxy.conf의 proxy_buffering off는 프록시가 답변 조각을 모아 뒀다가 한꺼번에 보내는 일을 막는 설정입니다. 모델의 스트리밍만 켠다고 화면까지 곧바로 조금씩 표시되는 것은 아닙니다.

권한 경계
문서 ID를 알고 있다는 사실은 접근 권한이 아닙니다

internal/application/repository/knowledge.go의 GetKnowledgeByID는 tenant_id와 id를 함께 조건으로 사용합니다. 검색 저장소도 internal/application/service/retriever/factory.go의 소유권 검증을 거쳐야 합니다.

공간 선택 헤더는 접근 요청의 문맥일 뿐입니다. 서버가 권한을 확인해야 합니다. 코드에 이런 경계가 있다는 사실만으로 모든 경로의 안전성이 검증되었다는 뜻은 아닙니다.

5디렉토리 구조 해부

폴더를 암기하기보다 “어느 질문을 어디에 물을지” 정하기
WeKnora/ ├── cmd/ │ ├── server/ 서버 시작·종료 │ └── desktop/ Wails 기반 데스크톱 진입점 ├── internal/ │ ├── router/ HTTP 경로·작업 처리기 연결 │ ├── handler/ 요청 해석과 응답 │ ├── application/ │ │ ├── access/ 자원 접근 규칙 │ │ ├── service/ 문서·세션·위키 등 업무 흐름 │ │ │ ├── chat_pipeline/ RAG 단계별 플러그인 │ │ │ └── retriever/ 검색 엔진 조립·색인 조정 │ │ └── repository/ DB 및 검색 저장소 접근 │ ├── infrastructure/ │ │ ├── docparser/ 파서 선택·결과 통합 │ │ ├── chunker/ 분할·제목 문맥·위치 계산 │ │ └── web_search/ 웹 검색 공급자 연결 │ ├── agent/ 반복 실행·도구·승인·문맥 압축 │ ├── models/ 모델 카탈로그·공급자·모델 호출 │ ├── sandbox/ 도구 실행 환경과 수명 관리 │ ├── stream/ Redis / 메모리 이벤트 스트림 │ ├── types/ 데이터 타입·인터페이스 │ └── container/ 구현체 조립 ├── docreader/ Python 파서·gRPC·OCR·테스트 ├── frontend/ Vue 앱·API·상태·UI 테스트 ├── migrations/ DB 스키마 변경 ├── config/ 기본 설정·프롬프트 템플릿 ├── cli/ 별도 Go 모듈의 CLI ├── client/ 별도 Go 모듈의 API 클라이언트 ├── mcp-server/ 이전 Python MCP 서버 ├── third_party/anydoc-go/ 네이티브 파서 연결·Rust 코드 ├── docker/ 서비스별 이미지 정의 ├── helm/ Kubernetes 배포 ├── website-docs/ 제품·운영·개발 문서 └── .github/workflows/ 언어·기능별 CI
알고 싶은 것먼저 읽을 곳그다음 이동
서버는 어디서 시작하나cmd/server/main.gocontainer.BuildContainer → router.NewRouter
파일이 왜 아직 검색되지 않나internal/application/service/knowledge_create.gointernal/application/service/knowledge_process.go의 처리 상태·색인 경로
검색 결과는 왜 달라졌나internal/application/service/chat_pipeline/질의 이해 → 검색 → 재정렬 → 병합 → 제한
Agent가 멈추지 않는다면internal/agent/engine.go반복 예산·취소·정체 감지·문맥 압축
답변이 한꺼번에 표시된다면frontend/src/api/chat/streame.tsNginx 버퍼링 → 서버 이벤트 → 렌더링 버퍼
공간 간 데이터가 섞일까internal/application/access/서비스 접근 검사 → 저장소의 공간 조건 → 검색 엔진 소유권

mcp-server/만 보고 현재 MCP 서버의 전부라고 판단하지 마세요. README의 v0.8.2 설명은 Python 서버를 이전 구현으로 분류하고, 현재는 internal/router/router.go에서 RegisterMCPServerRoutes로 공간별 /mcp/:endpoint_id 경로를 연결합니다. 비슷한 이름의 오래된 코드와 현재 진입점을 구분하는 것도 소스 탐색의 일부입니다.

코드 규모는 어느 정도인가

확장자파일 수물리적 줄 수
Go .go3,370862,852
Vue .vue257173,212
TypeScript .ts576131,518
Python .py12526,225
JavaScript .js141,837
Rust .rs41,127

집계 대상은 커밋 314a6fa의 Git 추적 파일 5,631개 중 위 확장자입니다. 빈 줄·주석·테스트·생성 코드·제3자 코드가 포함된 물리적 줄 수이며, 직접 작성한 운영 코드의 양과 같지 않습니다. 예를 들어 docs/의 Go 파일만 43,544줄이고 Swagger 생성 코드가 포함됩니다.

Go의 _test.go는 1,772개·360,231줄입니다. 나머지 Go 502,621줄도 생성 코드 등을 포함합니다. internal/의 Go는 760,205줄, frontend/의 Vue는 172,792줄입니다. 큰 저장소를 공부하는 가장 현실적인 방법은 전체 정독이 아니라, 한 기능의 경로와 그 경로의 테스트를 함께 읽는 것입니다.

6학습 포인트 (기술별)

기술 이름을 외우는 대신, 코드가 지키려는 조건을 찾아보기

6-1. Go: 병렬화와 취소를 함께 설계하기

internal/application/service/chat_pipeline/search.go는 sync.WaitGroup으로 검색 작업을 기다리고, 공유 결과 목록을 sync.Mutex로 보호합니다. internal/application/service/chat_pipeline/search_parallel.go는 더 바깥 단계에서 채팅 상태를 복제해 두 검색이 같은 슬라이스를 동시에 바꾸지 않게 합니다. “goroutine을 썼다”보다 누가 어떤 데이터를 수정하는지가 먼저입니다.

실습 아이디어는 공유 상태 표를 만드는 것입니다. 검색어·청크 결과·엔티티 결과·오류를 각각 읽기 전용, 작업별 소유, 병합 단계 소유로 분류해 보세요. 같은 주소를 가리키는 슬라이스를 단순 복사하면 충분한지 ChatManage.Clone의 구현과 대조할 수 있습니다.

cmd/server/main.go의 종료 경로도 좋은 예제입니다. 첫 종료 신호에서 HTTP 요청을 정리하고, 별도의 시간 예산을 가진 문맥으로 자원을 정리합니다. 이미 만료된 요청 정리 문맥을 그대로 자원 정리에 재사용하지 않는 이유를 설명해 보세요.

6-2. 검색: 후보 수집과 최종 판단의 역할 나누기

검색의 두 도구
임베딩과 하이브리드 검색
임베딩은 텍스트나 이미지를 숫자 벡터로 바꾸는 작업입니다. 벡터 검색은 표현이 달라도 의미가 가까운 자료를 찾는 데 사용합니다. 키워드 검색은 제품 코드·고유명사처럼 정확한 문자열에 강점이 있습니다. 하이브리드는 서로 다른 단서를 함께 활용하지만, 점수 결합과 재정렬 정책에 따라 결과가 달라집니다.

internal/application/service/retriever/keywords_vector_hybrid_indexer.go의 KeywordsVectorHybridRetrieveEngineService.BatchIndex를 읽어 보세요. 벡터 색인 경로에는 batchSize = 40, maxConcurrency = 5, windowSize = batchSize * maxConcurrency가 있습니다. 최대 200개 단위 창을 처리·저장한 뒤 다음 창으로 넘어가 모든 임베딩을 한꺼번에 붙잡지 않게 합니다.

같은 파일의 batchEmbedWithBackoff는 최초 호출을 포함해 최대 5회 시도하며, 재시도 대기의 시작값은 200ms입니다. 이를 업로드 작업의 MaxRetry(3)과 구분하세요. 앞은 처리 중 모델 호출의 재시도이고, 뒤는 큐 작업의 재시도입니다. 여러 단계의 재시도가 겹치면 요청 수와 대기 시간이 어떻게 늘 수 있을지 표로 계산해 볼 수 있습니다.

6-3. 텍스트 처리: 한국어에서 바이트와 글자 위치 구분하기

internal/infrastructure/chunker/splitter.go의 Chunk는 Start와 End를 rune 위치로 기록합니다. Content의 rune 수는 End - Start와 같아야 합니다. 앞에 붙일 제목 경로인 ContextHeader는 별도 필드에 둡니다.

// Go의 문자열 길이 차이를 보여 주는 학습 예시
text := "한글"
fmt.Println(len(text))                    // UTF-8 바이트: 6
fmt.Println(utf8.RuneCountInString(text)) // rune: 2

// internal/infrastructure/chunker/splitter.go의 핵심 조건
End - Start == utf8.RuneCountInString(Content)

rune은 유니코드 코드 포인트 단위이므로 모든 이모지나 결합 문자의 “눈에 보이는 한 글자”와 완전히 같지는 않습니다. 그래도 UTF-8 바이트 위치와 혼동하는 실수를 피하는 첫걸음입니다. 원문 강조 표시와 인용 위치를 정확히 연결하려면 위치 단위가 처음부터 끝까지 일관되어야 합니다.

설정과 구현을 함께 읽는 사례
“기본 청크 크기”에도 적용 경로가 있습니다

config/config.yaml은 chunk_size: 512, chunk_overlap: 50입니다. 반면 Go 분할기의 상수는 DefaultChunkSize = 512, DefaultChunkOverlap = 80입니다.

internal/infrastructure/chunker/strategy.go의 NormalizeSplitterConfig는 크기·겹침이 0 이하일 때 상수로 보정합니다. 따라서 이 정규화 경로에서 0을 “겹침 없음”으로 간주하면 안 됩니다. 실제 문서에는 지식베이스 설정과 처리 옵션이 반영되므로 YAML 하나만 읽고 모든 청크의 겹침이 50이라고 단정할 수도 없습니다.

검색 품질을 바꾸는 작은 설정도 원문 위치·저장량·모델 입력 길이까지 연결해 읽어야 실제 의미가 드러납니다. 실습으로 제목 경로가 붙은 임베딩 입력과 저장된 Content를 나란히 그려 보세요. 왜 두 문자열의 길이가 달라도 원문 위치는 그대로여야 하는지 이해할 수 있습니다.

6-4. Python·gRPC: 외부 도구를 하나의 계약으로 감싸기

docreader/proto/docreader.proto의 Read는 한 응답을, ReadStream은 메타데이터 후 이미지별 메시지를 반환합니다. 많은 페이지 이미지를 하나의 큰 응답에 담지 않도록 나눈 프로토콜입니다. 스트리밍 응답이어도 업로드된 입력 파일 자체가 자동으로 스트리밍 입력이 되는 것은 아닙니다.

실습 아이디어는 텍스트 1개와 이미지 300개가 있는 문서를 가정해 두 응답 형식을 그리는 것입니다. 한 메시지의 크기 제한과 전체 문서 처리 중 사용하는 메모리 제한이 왜 다른 문제인지 적어 보세요. 파싱 결과·이미지 저장·청크 분할의 담당 언어를 색깔 대신 세 열의 표로 나눠도 좋습니다.

6-5. Vue·SSE: 문자열보다 실행의 수명을 관리하기

useStream은 답변 문자열 외에도 로딩·진행·오류·중단 상태를 관리합니다. 오래된 스트림이 새 질문의 화면을 덮지 않도록 세대 번호를 확인하고, 재연결이 가능한 상태에서 30초 동안 이벤트가 없는 연결을 감지하는 타이머도 둡니다.

실습은 “질문 A 전송 → 질문 B 시작 → A의 늦은 이벤트 도착” 타임라인을 그리는 것입니다. 어느 조건이 A의 이벤트를 버리는지 찾고, 서버 작업 취소와 화면의 연결 중단이 같은지 따로 생각해 보세요. 화면 상태와 서버 실행 상태는 네트워크가 끊겼을 때 특히 어긋나기 쉽습니다.

6-6. 테스트 읽기: 성공 사례보다 경계 조건부터

internal/application/service/knowledge_create_test.go에는 저장 실패·레코드 생성 실패와 관련된 테스트가 있고, internal/application/service/retriever/composite_partial_test.go는 부분 결과의 계약을 다룹니다. internal/infrastructure/chunker/parent_child_offsets_test.go와 internal/agent/const_test.go처럼 함수 주변의 테스트를 읽으며 입력 → 예상 결과 → 지키려는 조건을 한 문장으로 요약해 보세요.

7하드웨어 / 시스템 요구사항

플랫폼 실행 자원과 모델 추론 자원을 분리해서 계산하기

공식 설치 문서 website-docs/01-getting-started/02-installation.md는 표준 Docker 배포의 출발점으로 CPU 4코어·RAM 8GB를 권장합니다. 이는 보장된 최소 사양이나 부하 시험 결과가 아니며, 로컬 모델 가중치 메모리는 포함하지 않는다고 명시합니다.

사용 형태확인 가능한 기준추가로 계산할 것
소스 읽기편집기와 검색 도구만 있으면 됨모델·Docker·GPU 없이도 이 문서의 읽기 실습 가능
표준 서버공식 문서 권장 4코어 / 8GB, Docker 20.10+와 Compose v2문서 파싱 동시성, DB 색인, 원문 저장량
LiteSQLite + 메모리 실행, 별도 Redis 불필요공식 문서에 고정 최소 RAM 수치 없음; 선택한 파서·모델 비용은 별도
외부 모델 API플랫폼 서버의 로컬 추론 GPU는 필수 아님네트워크 지연, 호출 요금, 공급자 제한
로컬 모델Ollama 등 별도 모델 서비스모델 크기·양자화·문맥 길이·동시 요청에 따른 RAM / VRAM
선택 서비스 추가Neo4j·Milvus·Langfuse 등 profile각 서비스의 상주 메모리·볼륨·운영 비용
소스 빌드Go 1.26, CGO 도구, Node 24 계열, 파서는 Python 3.10.18 계열anydoc 빌드에는 Rust도 필요; UI 빌드의 Node 힙 기본값은 4096MB

공식 설치 문서는 기본 ParadeDB 이미지의 x86 기준을 x86-64-v2로 설명합니다. 실제 호스트와 선택한 이미지의 지원 아키텍처를 확인해야 하며, “모든 ARM·모든 오래된 CPU에서 동일하게 동작한다”는 뜻은 아닙니다. Kubernetes 요구 버전은 helm/Chart.yaml에서도 확인할 수 있습니다.

7-1. 파일 크기 제한은 전체 메모리 상한이 아니다

docker-compose.yml의 docreader 기본값은 DOCREADER_GRPC_MAX_WORKERS=4, DOCREADER_GRPC_MAX_FILE_SIZE_MB=50입니다. docreader/config.py는 파일 크기 설정을 바이트로 환산합니다. 50MiB급 입력 네 개의 데이터량만 약 200MiB이고, 여기에 압축 해제·PDF 렌더링·이미지·라이브러리 버퍼가 더해질 수 있습니다.

# Compose에 선언된 일부 기본값 — 실행 명령이 아닌 설정 예시
DOCREADER_GRPC_MAX_WORKERS=4
DOCREADER_GRPC_MAX_FILE_SIZE_MB=50
DOCREADER_PREPROCESS_MAX_WORKERS=1
DOCREADER_PREPROCESS_MEMORY_MB=2048
DOCREADER_PDF_RENDER_MAX_WORKERS=1

DOCREADER_PREPROCESS_MEMORY_MB는 이미지 전처리 설정입니다. 이를 docreader 프로세스 전체나 컨테이너 전체의 메모리 제한으로 읽으면 안 됩니다. 큰 파일이 많을 때는 병렬 개수를 늘리는 것이 처리량보다 메모리 압박을 먼저 키울 수 있습니다.

7-2. 현재 HEAD가 보여 주는 메모리 수명 문제

커밋 314a6fa의 제목은 큰 읽기 요청 후 메모리 회수에 관한 수정입니다. docreader/utils/request_memory.py는 16MiB 이상 입력에 대해 스레드별 회수 표시를 남기고, RequestThreadPoolExecutor._run의 finally에서 작업 반환 후 gc.collect()와 사용 가능한 Linux malloc_trim(0)을 호출합니다.

처리 함수 안에서는 gRPC가 요청 인자를 아직 붙잡을 수 있다는 점을 고려한 위치 선택입니다. 모든 요청에 수집을 강제하는 방식도 아니며, Linux가 아니거나 해당 함수가 없으면 같은 경로가 모두 실행되지는 않습니다. 이 코드만으로 메모리가 일정량까지 내려간다거나 성능이 몇 배 좋아진다고 결론낼 수는 없습니다.

메모리 비유

작업대에서 종이를 치우는 것과, 비워진 작업대를 창고에 돌려주는 것은 다릅니다. 객체 참조가 사라지는 시점, Python이 객체를 회수하는 시점, 할당기가 운영체제에 메모리를 돌려주는 시점도 구분해야 합니다.

7-3. 저장 공간은 벡터 외의 데이터도 포함한다

단순 계산으로 float32 1,024차원 벡터 10만 개는 100,000 × 1,024 × 4 = 409,600,000바이트, 약 391MiB입니다. 이는 예시일 뿐 WeKnora의 모든 모델이 1,024차원을 쓴다는 뜻이 아닙니다. 실제 저장 공간에는 원문·이미지·청크 본문·검색 색인·DB 메타데이터·백업이 더해집니다.

배포 형태를 읽을 때
자체 호스팅과 외부 전송 여부는 별도 선택입니다

원문을 내 서버에 저장해도 외부 임베딩·채팅·OCR·파싱 API를 선택하면 관련 데이터가 외부로 전송될 수 있습니다. Lite 문서의 로컬 처리 설명도 실제 선택한 모델과 파서 설정을 함께 확인해야 합니다.

현재 설치 문서는 데스크톱 앱의 공식 설치 프로그램이 아직 배포되지 않았다고 설명합니다. 편의 기능을 소개한 화면과 지금 이용 가능한 배포 산출물을 구분해 보세요.

8직접 해볼 수 있는 실습 과제

실행 환경 없이도 가능한 다섯 과제 — 소스를 읽고 결과물을 남기기

다음 과제는 코드를 실행하거나 저장소 파일을 바꾸지 않고 할 수 있습니다. 읽은 내용을 호출 지도·표·의사코드로 다시 표현하는 연습입니다. 예상 시간을 채우기보다 완료 기준을 자신의 말로 설명할 수 있는지 확인하세요.

과제 1 · 입문

문서 하나의 접수증 만들기 30~45분

목표: 업로드와 검색 준비가 왜 별도 상태인지 설명합니다.

읽을 곳: internal/application/service/knowledge_create.go의 CreateKnowledgeFromFile, internal/application/service/knowledge_process.go의 ProcessDocument와 processChunks.

할 일: 파일 검사·파일 저장·DB 등록·큐 등록·파싱·색인의 여섯 단계를 화살표로 연결하세요. 각 화살표 옆에 문서 ID, 공간 ID, 파일 경로 중 무엇이 필요한지 적습니다.

산출물: 호출 지도 한 장과 “파일은 있는데 색인은 없다”가 가능한 이유 세 문장.

완료 기준: DB 등록 실패와 큐 등록 실패에서 반환값·파일 정리가 어떻게 다른지 설명할 수 있습니다.

# 저장소 루트에서 사용하는 읽기 전용 검색 예시
rg -n 'func .*CreateKnowledgeFromFile' internal/application/service/knowledge_create.go
rg -n 'func .*ProcessDocument|func .*processChunks' internal/application/service/knowledge_process.go
rg -n 'ParseStatus|EnableStatus|MaxRetry|Enqueue' internal/application/service/knowledge_create.go
과제 2 · 입문 → 중급

한국어 청크의 위치 장부 만들기 45~60분

목표: 원문 위치와 모델 입력이 달라질 수 있음을 이해합니다.

읽을 곳: internal/infrastructure/chunker/splitter.go의 Chunk와 EmbeddingContent, internal/infrastructure/chunker/strategy.go의 NormalizeSplitterConfig.

할 일: 짧은 한국어 매뉴얼 두 문단을 종이에 쓰고, 직접 정한 경계로 세 조각을 만드세요. 각 조각의 rune 시작·끝, 본문, 별도 제목 경로를 표에 적습니다. 단순화한 수작업 분할이며 실제 적응형 알고리즘의 출력을 재현했다고 주장하지 않습니다.

산출물: Content와 ContextHeader가 나뉜 장부.

완료 기준: 제목 경로를 모델 입력 앞에 붙여도 원문 Start가 바뀌면 안 되는 이유와, 겹침 0을 넣었을 때 정규화 함수가 선택하는 값을 설명할 수 있습니다.

과제 3 · 중급

검색 실패의 네 가지 얼굴 구분하기 60~90분

목표: 빈 결과, 전체 실패, 부분 실패, 재정렬 실패를 분리합니다.

읽을 곳: internal/application/service/chat_pipeline/search_parallel.go, internal/application/service/chat_pipeline/rerank.go, internal/application/service/retriever/composite.go와 internal/application/service/retriever/composite_partial_test.go.

할 일: ① 모든 검색 결과 없음 ② 청크 검색 성공·엔티티 검색 실패 ③ 검색 엔진 자체 오류 ④ 검색 성공·재정렬 API 실패를 가정합니다. 각 경우에 남는 결과, 반환 오류, 다음 단계 진행 여부를 표로 만듭니다.

산출물: 네 행의 실패 계약 표와 근거가 되는 함수 위치.

완료 기준: “오류가 있으면 무조건 결과도 없다”와 “결과가 있으면 모두 완전하다”가 왜 둘 다 잘못된 가정인지 코드로 설명할 수 있습니다.

과제 4 · 중급 → 고급

Agent 실행을 상태표로 시뮬레이션하기 60~90분

목표: Agent의 능력보다 종료·재개 조건을 이해합니다.

읽을 곳: internal/agent/engine.go의 withinIterationBudget와 runReActIteration, internal/agent/act.go의 executeToolCalls.

할 일: 첫 응답은 검색 도구 호출, 두 번째는 문서 읽기, 세 번째는 최종 답변이라고 가정합니다. 여기에 “두 번째 응답이 출력 길이 제한으로 잘림”과 “사용자가 실행을 취소함” 분기를 추가합니다.

산출물: 턴·라운드·도구·종료 이유를 구분한 상태 전이표.

완료 기준: 불완전한 인자로 도구를 실행하지 않는 분기를 찾고, 대화 기록 복원과 한 턴의 실행 상태가 다른 수명이라는 점을 설명할 수 있습니다.

과제 5 · 고급

저장소 교체 설계를 검토하기 90~120분

목표: 인터페이스와 권한 검사를 유지하며 구현을 교체하는 방법을 설계합니다.

읽을 곳: internal/types/interfaces/retriever.go, internal/application/service/retriever/factory.go, internal/container/container.go.

할 일: 새 검색 저장소를 추가한다고 가정하고, 지원 검색 방식·색인·검색·삭제·오류를 의사코드로 적습니다. 존재하지 않는 저장소, 다른 공간의 저장소, 잠시 연결이 안 되는 저장소를 서로 다른 분기로 둡니다.

산출물: 구현 책임 표, 가짜 구현의 반환 예시, 등록 위치를 표시한 설계 메모.

완료 기준: 저장소 ID로 구현체를 얻는 것과 접근 권한을 확인하는 것이 왜 별도 절차인지 설명합니다. DB마다 지원 기능이 다를 때 Support()가 필요한 이유도 적습니다.

학습 기록 방법
함수 이름 옆에 “이 함수가 보장하는 것”을 쓰세요

“검색한다”보다 “지원 검색 방식에 맞는 구현체에 위임한다”, “에러 처리”보다 “일부 결과는 남기되 누락을 오류로 알린다”처럼 적으면 좋습니다. 코드를 다시 보지 않고 그 보장을 설명할 수 있을 때 다음 단계로 넘어가세요.

9관련 기술 심화 학습 로드맵

6주 동안, 파일 하나의 흐름에서 시스템의 경계로 넓혀 가기

HTTP 요청·JSON·함수·배열의 기본 개념을 알고 있다는 출발점입니다. Go나 Vue가 처음이라면 첫 주를 더 길게 잡아도 됩니다. 시간표는 학습 제안이며, 한 주의 완료 조건을 설명할 수 있으면 다음 단계로 이동합니다.

주차배울 주제WeKnora에서 읽을 곳남길 결과물
1주Go 구조체·인터페이스·오류·문맥, HTTP 계층 cmd/server/main.go, internal/types/interfaces/retriever.go Handler·Service·Repository의 역할을 구분한 지도
2주문서 상태·중복 검사·비동기 작업·재시도 internal/application/service/knowledge_create.go, internal/application/service/knowledge_process.go 성공·실패·재처리의 상태표와 과제 1
3주청크·임베딩·키워드 / 벡터 검색·재정렬 internal/infrastructure/chunker/, internal/application/service/retriever/, internal/application/service/chat_pipeline/ 문서 세 개·질문 열 개로 구성한 평가 자료 초안
4주Go 동시성·오류 계약·테스트 대역 internal/application/service/chat_pipeline/search_parallel.go, internal/application/service/retriever/composite_partial_test.go 공유 상태 소유권 표와 과제 3
5주ReAct·도구 호출·문맥 예산·사용자 개입 internal/agent/engine.go, internal/agent/act.go, internal/agent/approval/ 정상 종료·취소·보류를 나눈 상태 전이표
6주Vue 스트림 상태·권한·배포·관측 frontend/src/api/chat/streame.ts, internal/application/service/retriever/factory.go, 빌드·CI 설정 브라우저부터 모델까지의 지연·오류·데이터 이동 지도

검색 품질을 공부할 때는 질문 세트를 먼저 만든다

3주 차 평가 자료에는 정확한 제품 코드 질문, 표현을 바꾼 질문, 두 문서를 비교하는 질문, 자료에 답이 없는 질문을 섞어 보세요. 각 질문에 “정답 문서”와 “근거 문단”을 직접 지정합니다. 당장 실행하지 않아도 어떤 결과를 성공으로 볼지 정할 수 있습니다.

나중에 실험할 때는 청크 크기·검색 후보 수·재정렬을 한 번에 모두 바꾸지 말고, 하나씩 바꾸며 근거가 검색되었는지와 답변이 근거를 따르는지를 따로 기록하세요. 좋은 RAG 학습은 그럴듯한 답변을 모으는 일보다, 어떤 근거를 찾았고 어디서 실패했는지 설명하는 일에서 시작합니다.

주차별 공통 질문
이 기능의 입력·출력·소유자·실패 조건은 무엇인가

인터페이스, 큐, 파서, 도구, 스트림 모두 이 네 질문으로 읽을 수 있습니다. 모델 이름이 바뀌어도 적용되는 시스템 설계 습관입니다.

10핵심 키워드 사전

앞에서 본 개념을 코드의 역할과 연결해 다시 확인하기
용어쉬운 설명
지식베이스문서와 검색 설정을 묶는 단위입니다. 폴더 하나와 완전히 같은 개념은 아닙니다.
RAG자료를 먼저 검색하고 그 자료를 참고해 답변을 만드는 방식입니다.
파싱PDF·문서 파일에서 텍스트·표·이미지 등 사용할 수 있는 내용을 추출하는 과정입니다.
OCR스캔이나 사진 속 글자를 텍스트로 읽는 기술입니다. 원래 텍스트가 있는 PDF 추출과는 다릅니다.
청크검색과 모델 입력을 위해 나눈 문서 조각입니다. 문맥과 원문 위치를 함께 관리해야 합니다.
겹침인접 청크가 일부 내용을 함께 가지게 하는 방식입니다. 경계의 정보 손실과 중복 저장 사이의 선택입니다.
부모·자식 청크작은 조각으로 검색하고 더 넓은 문맥을 연결하기 위해 크기가 다른 조각을 관계로 묶는 방식입니다.
임베딩텍스트나 이미지를 검색에 쓸 숫자 벡터로 바꾸는 작업입니다.
벡터 차원벡터 한 개를 구성하는 숫자의 개수입니다. 모델·저장 공간·색인 설정과 연결됩니다.
BM25질문 단어가 문서에서 얼마나 중요한지 등을 점수로 만드는 키워드 검색 방법입니다.
하이브리드 검색키워드와 벡터 등 서로 다른 검색 단서를 함께 활용하는 방식입니다.
재정렬먼저 찾은 후보를 질문과 다시 비교해 더 관련 있는 순서로 평가하는 단계입니다.
Top-K상위 K개 결과를 사용한다는 뜻입니다. 후보 수집 단계와 최종 문맥 단계의 K는 다를 수 있습니다.
ReAct모델 판단, 도구 실행, 결과 관찰을 반복해 다음 행동을 정하는 Agent 실행 방식입니다.
도구 호출모델이 함수 이름과 인자를 요청하고 앱이 실제 함수를 실행하는 구조입니다. 모델의 텍스트만으로 실행되지는 않습니다.
MCPAI 앱과 외부 도구·자료를 연결하는 공통 프로토콜입니다. 연결 자체가 무제한 권한을 의미하지 않습니다.
문맥 예산모델에 보낼 대화·자료·도구 정의와 출력에 사용할 수 있는 토큰의 한도입니다.
문맥 압축오래된 대화를 요약하거나 줄여 제한 안에 다음 요청을 넣는 처리입니다. 원문 전체 보존과는 다른 목적입니다.
작업 큐오래 걸리는 일을 요청 처리와 분리해 실행하도록 맡기는 구조입니다. 내구성과 재시도는 구현에 따라 달라집니다.
멱등성같은 작업이 반복되어도 의도하지 않은 중복 효과가 생기지 않도록 만드는 성질입니다.
지수 백오프재시도 사이의 대기 시간을 점차 늘려, 실패 중인 서비스에 요청이 몰리지 않게 하는 방법입니다.
공간 / 테넌트문서·사용자·설정을 구분하는 서비스의 논리적 경계입니다. DB 조건과 접근 검사가 함께 필요합니다.
RBAC사용자의 역할에 따라 할 수 있는 일을 정하는 권한 관리 방식입니다.
Principal현재 행동의 주체입니다. 웹 사용자 외에 임베드 방문자나 MCP 호출 주체도 구분할 수 있습니다.
SSE열린 HTTP 연결을 통해 서버가 이벤트를 계속 보내는 방식입니다. 답변·도구 진행·완료를 전달할 수 있습니다.
gRPC / Protobuf서비스 간 호출 방식과 메시지 형식을 정하는 도구입니다. 언어가 달라도 같은 계약을 사용할 수 있습니다.
runeGo에서 유니코드 코드 포인트를 나타내는 타입입니다. UTF-8 바이트 수와 구분해야 합니다.
어댑터서로 다른 외부 서비스 사용법을 내부 공통 인터페이스에 맞추는 연결 구현입니다.
관측 가능성로그·실행 구간·토큰 사용량 등으로 시스템 안에서 무슨 일이 일어났는지 파악할 수 있는 성질입니다.
RSS 메모리프로세스가 실제 물리 메모리에 올려 두고 있는 양을 나타내는 지표입니다. 살아 있는 객체 크기만의 합은 아닙니다.

용어를 외웠는지보다 소스의 어느 경계에서 쓰이는지 설명해 보세요. 예를 들어 “큐”는 업로드와 처리 사이, “SSE”는 서버 실행과 화면 사이, “어댑터”는 공통 서비스와 외부 구현 사이에 놓입니다.

11참고 링크

기능 소개, 구현 근거, 운영 문서를 구분해서 읽기

공식 저장소와 제품 문서

본문의 핵심 소스 근거

관련 기술의 공식 학습 자료

현재 상태 (2026-10-23 확인)

항목확인 값 / 읽는 방법
선정 근거GitHub Trending Go Monthly #8 · 2026-10-23 분석
스타 / 포크35,175 / 4,724 · 2026-10-23 GitHub 기준
주 언어 / 생성일Go / 2025-07-22
최근 push2026-10-23 · 저장소 활동 시점이며 릴리스 날짜와 다름
최신 릴리스v0.8.3 · 2026-10-14
분석 HEAD314a6fafd718724b5e6a11b42650b51db6b588d8 · 2026-10-23
최근 커밋 내용큰 docreader 읽기 요청 후 메모리 회수 수정 · #4628
코드 규모Git 추적 파일 5,631개; Go 862,852줄, Vue 173,212줄, TypeScript 131,518줄, Python 26,225줄. JavaScript·Rust를 포함한 여섯 확장자 합계 1,196,771줄. 빈 줄·테스트·생성 코드 등 포함.
테스트 규모Go의 _test.go 1,772개·360,231줄. 테스트 통과율이나 커버리지 수치가 아님.
라이선스 표시GitHub 메타는 NOASSERTION. README는 MIT로 소개하고, LICENSE 본문도 제3자 구성요소를 제외한 WeKnora에 MIT를 명시.
배포 형태표준 Compose·Helm, Lite, 소스 기반 데스크톱 앱. 형태별 기능·의존성이 다름.
쓰기 전에 알아둘 한계
구현 근거와 성능·운영 보장을 구분하세요

이 글의 소스 설명은 커밋 314a6fa 기준입니다. 최신 릴리스 v0.8.3과 같은 스냅샷이라고 가정하면 안 됩니다. 기능 소개 중 모델 호환성·대규모 처리 성능은 README 또는 공식 문서의 설명이며, 실제 모델·문서·부하에 대한 재현 시험 결과를 뜻하지 않습니다.

CI에 검사가 정의되어 있어도 해당 커밋에서 모두 통과했다는 보장은 없습니다. 처리량·메모리·정확도의 실행 실측값은 제시하지 않습니다. 줄 수에는 테스트와 생성 코드가 포함되며, 전체 누적 커밋 수나 기여자 비율은 이 자료의 검증 범위가 아닙니다.

MIT 소개와 GitHub의 NOASSERTION 표시는 구분해야 합니다. 재배포에 앞서 LICENSE 원문과 제3자 구성요소 고지를 함께 확인하세요. 검색 결과와 위키 페이지도 자동 생성된 지식이므로 원문 근거와 최신성을 검토할 필요가 있습니다.