본문으로 바로 가기

모델 관리

‘설정 → 모델’에서 대화, 벡터, 리랭킹, 비전, 음성 모델을 추가한 뒤 지식 베이스나 에이전트에서 필요에 따라 선택합니다. 로컬 Ollama와 원격 모델을 조합할 수도 있습니다. 예를 들어 로컬 모델로 벡터를 생성하고 원격 모델로 답변을 생성할 수 있습니다.

스크린샷 준비 중
모델 설정: 추가한 모델을 유형별로 관리

모델 목록(이름, 유형, 출처, 기본 모델 표시)과 ‘모델 추가’ 양식, 연결 테스트 결과를 보여 줍니다.

website-docs/public/screenshots/settings-models.png
모델 설정: 추가한 모델을 유형별로 관리

모델을 추가할 때는 연결 설정과 인덱스 호환성을 확인해야 합니다.

  • 벡터 모델을 바꾸면 인덱스를 재구축해야 합니다. 모델이 벡터의 의미 공간과 차원을 결정하므로 새 벡터와 기존 벡터를 바로 섞어 쓸 수 없습니다.
  • 저장 전에 연결을 테스트합니다. 서비스 주소, 자격 증명, 모델 이름이 유효한지 확인한 뒤 지식 베이스나 에이전트에 사용합니다.

모델 유형, 설정 필드, 사용 상태는 아래 설명을 참고하세요.

모델 유형 선택

유형용도
대화 모델질의응답, 요약, 지능형 추론 내용 생성
벡터 모델문서와 질문을 벡터로 변환하여 의미 검색 지원
리랭킹 모델검색한 조각의 순서 재정렬
비전 모델문서나 대화의 이미지 인식
음성 모델오디오를 텍스트로 전사

추가와 연결 검증

모델 설정에서 유형과 제공자를 선택하고 실제 모델 이름, 서비스 주소, 자격 증명을 입력한 뒤 테스트를 통과하면 저장합니다. 벡터 모델은 인덱스 차원과 맞아야 합니다. 대화 및 비전 모델의 컨텍스트 창은 대화 이력 압축이 올바르게 실행되도록 서비스가 실제 지원하는 크기를 사용해야 합니다.

연결 주소를 수정할 때는 저장된 자격 증명으로 테스트할 수 있습니다. 모델 디버거는 실제 요청을 보내고 소요 시간, 민감 정보를 가린 요청, 응답 결과를 표시합니다. 벡터 차원, 리랭킹 점수, 스트리밍 출력을 확인하는 데 사용할 수 있습니다.

참조 확인과 설정 조정

지식 베이스와 에이전트는 모델 참조를 저장합니다. 모델을 삭제하기 전에 의존성 상세를 확인해야 합니다. 내장 모델은 YAML 설정으로 관리하므로 설정 파일에서 유지보수해야 합니다. 호출량과 캐시 사용 현황은 관측 가능성과 감사에서 함께 확인할 수 있습니다.

설정과 호출 참고

모델 유형과 용도

모델 유형은 internal/types/model.go에 정의되어 있습니다.

go
const (
    ModelTypeEmbedding   ModelType = "Embedding"   // 임베딩 모델
    ModelTypeRerank      ModelType = "Rerank"      // 리랭킹 모델
    ModelTypeKnowledgeQA ModelType = "KnowledgeQA" // 지식 질의응답 모델
    ModelTypeVLLM        ModelType = "VLLM"        // VLLM 모델
    ModelTypeASR         ModelType = "ASR"         // ASR 모델
)
유형프런트엔드 식별자클라이언트 패키지인터페이스용도
KnowledgeQAchatinternal/models/chatChat / ChatStream(Tools, Thinking, 멀티모달 메시지 지원)지식 질의응답, Agent 추론, 요약 / 질문 생성 / 그래프 추출 등 모든 LLM 호출
Embeddingembeddinginternal/models/embeddingEmbed / BatchEmbed(GetDimensions 포함)벡터 검색 인덱스와 질의를 위한 텍스트 벡터화
Rerankrerankinternal/models/rerankRerank(query, documents)RankResult 반환검색 결과 정밀 순위 조정
VLLMvllminternal/models/vlmPredict(imgBytes, prompt)비전 언어 모델(VLM), 문서 이미지 이해 / 멀티모달 파싱
ASRasrinternal/models/asrTranscribe(audioBytes, fileName)이 텍스트와 구간별 타임스탬프 반환오디오 전사(자동 음성 인식)

프런트엔드와 백엔드 유형 매핑은 internal/handler/model.gomodelTypeToFrontend()(KnowledgeQA -> chat 등)를 참고하세요.

모델 출처(ModelSource)의 핵심 값은 local(로컬 Ollama로 실행)과 remote(원격 API) 두 가지입니다. 나머지 과거 값(aliyun, zhipu, openai 등)은 호환성을 위해 유지하며 라우팅 동작은 remote + 해당 provider와 같습니다.

모델 설정 필드

모델 엔터티 types.ModelParameters(internal/types/model.goModelParameters):

이름유형기본값설명
base_urlstring빈 값(Provider의 DefaultURLs 사용 가능)모델 API 주소, 생성/수정 시 SSRF 검증(ValidateURLForSSRF) 수행
api_keystring빈 값API 키, AES-256-GCM으로 암호화하여 DB 저장(ModelParameters.Value/Scan), PUT /models/:id/credentials 하위 리소스로만 수정 가능
interface_typestring빈 값(VLM: local 기본 ollama, remote 기본 openai)인터페이스 프로토콜 유형
embedding_parameters.dimensionint0벡터 차원
embedding_parameters.truncate_prompt_tokensint0입력을 자를 token 수
embedding_parameters.supports_dimension_overrideboolfalse요청 수준 차원 재정의(dimensions 매개변수) 지원 여부
parameter_sizestring빈 값Ollama 모델 매개변수 규모(예: "7B"), 백엔드가 관리하며 프런트엔드에서 수정 불가
providerstring빈 값(BaseURL로 자동 감지)제공자 식별자
extra_configmap[string]stringnil제공자 전용 설정(예: Azure의 api_version)
custom_headersmap[string]stringnil추가 사용자 정의 HTTP 요청 헤더(OpenAI SDK extra_headers와 유사, Authorization, api-key 등 예약 헤더는 런타임에 무시)
supports_visionboolfalseChat 모델의 이미지 멀티모달 입력 수락 여부
context_windowint0(200000으로 대체)대화/VLM 컨텍스트 창(token). 에이전트의 이력 압축이 이 상한에 따라 동작. 빈 값이면 기본 200K 사용. 너무 높으면 압축이 제때 실행되지 않으므로 서비스가 실제 지원하는 창 크기 입력
max_concurrencyint0(전역 model.max_concurrency로 대체)이 모델의 백그라운드 작업 동시 실행 상한(chat/vlm/embedding에만 적용)
app_id / app_secretstring빈 값WeKnoraCloud 전용 자격 증명, app_secret은 AES 암호화 저장

모델 수준 필드에는 name(런타임에 실제 호출하는 모델 이름), display_name, type, source, is_default(동일 (tenant_id, type) 그룹에서 유일한 기본 모델), is_builtin, managed_by, status(active / downloading / download_failed)도 있습니다.

관리 API(internal/router/router.go)

메서드 & 경로설명
GET /models/providersmodel_type별 지원 제공자 목록 조회(ListModelProviders)
POST /models / GET /models / GET /models/:id / PUT /models/:id / DELETE /models/:id모델 CRUD
PUT /models/:id/credentials, DELETE /models/:id/credentials/:field자격 증명 하위 리소스. PUT /models/:id 요청 본문의 api_key는 강제로 무시하고 경고 기록
POST /models/:id/debug모델 디버깅(아래 참고)
GET /models/weknoracloud/statusWeKnoraCloud 자격 증명 상태

모델 상태 확인 / 연결 테스트

두 메커니즘 모두 서버 측에서 자격 증명을 보유하며 평문 키를 반환하지 않습니다.

  1. 연결 테스트(internal/handler/initialization.go, 모델 생성/편집 양식의 ‘연결 테스트’ 버튼용):

    • POST /initialization/remote/check — Chat 모델(CheckRemoteModel / checkChatModelConnection)
    • POST /initialization/embedding/test — Embedding(TestEmbeddingModel)
    • POST /initialization/rerank/check — Rerank(CheckRerankModel)
    • POST /initialization/asr/check — ASR(CheckASRModel)
    • POST /initialization/multimodal/test — VLM 멀티모달 파싱(TestMultimodalFunction)

    요청 본문 ModelTestRequest에는 modelId를 포함할 수 있습니다. fillSecretsFromStoredModel은 요청에서 빠진 APIKey / AppSecret을 저장된 모델에서 복호화하여 채웁니다. 따라서 프런트엔드가 평문 키를 얻을 필요도 없고 얻을 수도 없는 상태에서 ‘BaseURL을 바꾸고 기존 키로 한 번에 검증’할 수 있습니다. buildTestModel은 요청을 DB에 저장하지 않는 임시 *types.Model로 변환하며 프로덕션 경로와 같은 ConfigFromModel 매핑을 공유합니다.

  2. 모델 디버거(POST /models/:id/debug, ModelHandler.DebugModel): 저장된 모델에 유형별 실제 호출을 수행하고 정규화된 전체 응답을 반환합니다. Chat은 스트리밍을 사용하고 stream_events / thinking 관측 항목을 집계합니다. Embedding은 벡터와 차원, Rerank는 점수 결과를 반환하며 VLM / ASR은 파일 업로드를 받습니다. 응답에는 elapsed_ms, 민감 정보를 가린 요청 미리보기(redactedDebugConfig가 secret/token/api_key 계열 필드 숨김), observations가 포함됩니다.

내장 모델 메커니즘

internal/types/builtin_models_config.go는 선언적 내장 모델을 구현합니다. 시작 시 config/builtin_models.yaml(또는 BUILTIN_MODELS_CONFIG 지정 경로, 템플릿은 config/builtin_models.yaml.example)을 읽고 각 항목을 models 테이블에 UPSERT합니다. is_builtin=true, managed_by="yaml", 기본 tenant_id=10000(DefaultBuiltinModelTenantID)이며 모든 테넌트에 표시됩니다.

주요 동작(LoadBuiltinModelsConfig):

  • 모든 문자열 필드는 ${ENV_NAME} 환경 변수 보간을 지원합니다. 설정하지 않은 변수는 설정 오류를 드러내기 위해 리터럴로 유지합니다.
  • 시작할 때마다 id 기준 UPSERT하고 deleted_at을 강제로 NULL로 재설정합니다(파일에 다시 나타난 항목은 복구됨).
  • 설정 이탈 정리: managed_by='yaml'이지만 파일에 id가 없는 행은 소프트 삭제합니다. YAML에서 항목을 삭제하는 것이 내장 모델을 내리는 공식 방법입니다.
  • 관리자가 런타임에 행을 직접 관리하도록 전환하면(managed_by를 빈 값으로) YAML 로더는 해당 행을 건너뜁니다("preserving runtime override").
  • is_default: true 항목은 먼저 같은 (tenant_id, type) 그룹의 다른 기본 설정을 해제하여 API 경로와 동일한 유일 기본값 불변식을 유지합니다.
  • 검증 규칙: id는 비어 있지 않고 ≤64자(ModelIDMaxLen), type은 KnowledgeQA | Embedding | Rerank | VLLM | ASR, status는 유효한 값 또는 빈 값이어야 합니다. YAML 파싱 실패 시 동기화를 중단하고 설정 이탈 정리도 수행하지 않습니다.

YAML 예시(builtin_models.yaml.example 발췌):

yaml
builtin_models:
  - id: builtin-llm-default
    type: KnowledgeQA
    source: remote
    is_default: true
    name: ${LLM_MODEL_NAME}
    parameters:
      base_url: ${LLM_BASE_URL}
      api_key: ${LLM_API_KEY}
      provider: ${LLM_PROVIDER}

로컬 모델 다운로드(Ollama)

로컬 모델 수명 주기는 internal/models/utils/ollama/ollama.goOllamaService가 관리합니다(IsModelAvailable / PullModel / EnsureModelAvailable / ListModelsDetailed / DeleteModel 등). HTTP 진입점은 internal/handler/initialization.go에 있습니다.

경로설명
GET /initialization/ollama/statusOllama 서비스 가용성
GET /initialization/ollama/models로컬에 있는 모델 목록
POST /initialization/ollama/models/check모델 다운로드 여부 일괄 확인
POST /initialization/ollama/models/download비동기 다운로드(downloadModelAsync + pullModelWithProgress, 모델에 status=downloading 기록)
GET /initialization/ollama/download/progress/:taskId, GET /initialization/ollama/download/tasks다운로드 진행률 / 작업 목록

주의: cmd/download/duckdb/duckdb.go는 모델과 관련이 없습니다. 이미지 빌드 시 데이터 분석 도구용 DuckDB spatial, excel 확장을 미리 내려받습니다. 모델 가중치 다운로드는 Ollama 경로에서만 발생합니다.

모델 사용량 통계

  • Token 사용량: types.TokenUsage(internal/types/chat.go)는 prompt_tokens / completion_tokens / total_tokens와 prompt cache 세부 항목(cache_read_tokens / cache_write_tokens / cache_miss_tokens / cache_status)을 기록합니다. 각 Chat 구현은 internal/models/chat/usage.gologUsage로 통일된 구조화 로그 행을 출력합니다.

    go
    logger.Infof(ctx,
        "[LLM Usage] model=%s, purpose=%s, prompt_prefix=%s, prompt_tokens=%d, completion_tokens=%d, ...",
        ...)

    purposetypes.WithLLMCallMetadata에서 가져오며(예: document_summary, entity_extraction) 용도별 집계에 사용할 수 있습니다.

  • 분산 추적: Langfuse를 켜면 모델 유형마다 langfuse_wrapper.go 데코레이터가 호출(usage 포함)을 trace/span으로 보고합니다.

  • 스트리밍 응답: usage는 마지막 StreamResponse 이벤트와 함께 반환합니다(모델 디버거는 이를 usage 필드에 집계).

  • 동시 실행 현황: 앞 절에서 설명한 대로 GET /system/admin/runtime/queues가 모델별 실시간 active / waiting / limit을 제공합니다.

모델 호출과 구현 참고

Provider 추상화

internal/models/provider/provider.go는 다중 제공자 어댑터의 통합 레지스트리를 정의합니다.

go
type Provider interface {
    // Info는 제공자의 메타데이터를 반환합니다.
    Info() ProviderInfo
    // ValidateConfig는 제공자 설정을 검증합니다.
    ValidateConfig(config *Config) error
}

각 제공자는 자신의 파일(예: provider/openai.go, provider/aliyun.go)에서 init()을 통해 Register()를 호출하여 등록합니다. ProviderInfo에는 DisplayName, Description, 모델 유형별 DefaultURLs, 지원 ModelTypes, RequiresAuth, 선택적 ExtraFields가 포함됩니다(예: Azure OpenAI는 기본값 2024-10-21인 추가 필드 api_version 선언).

지원 제공자 목록

AllProviders()(provider/provider.go)가 반환하는 전체 목록입니다(총 27개, 각 제공자가 자신의 파일에서 init()으로 등록). 마지막 행의 Ollama는 목록에 포함되지 않고 source=local 독립 경로를 사용하며, 비교 편의를 위해 함께 표시했습니다.

Provider 식별자이름설명
genericGeneric모든 OpenAI 호환 / 사용자 정의 배포(기본 대안)
weknoracloudWeKnoraCloudWeKnora 클라우드 서비스(https://weknora.weixin.qq.com 하드코딩, AppID/AppSecret 자격 증명 사용)
aliyunAlibaba Cloud DashScope
zhipuZhipu AI(GLM 계열)
volcengineVolcengine Ark
hunyuanTencent Hunyuan
siliconflowSiliconFlow
deepseekDeepSeek
minimaxMiniMax
moonshotMoonshot(Kimi)
modelscopeModelScope
qianfanBaidu Qianfan
qiniuQiniu Cloud
openaiOpenAI다섯 모델 유형 모두 지원
anthropicAnthropic Claude독립 Messages 프로토콜 구현
geminiGoogle GeminiEmbedding은 전용 API 사용
openrouterOpenRouter
litellmLiteLLM(자체 호스팅 OpenAI 호환 프록시)기본 URL은 자리표시자이며 loopback은 SSRF_WHITELIST에 추가 필요
requestyRequesty
jinaJina AIEmbedding과 Rerank
mimoXiaomi MiMo
longcatMeituan LongCat AI
lkeapTencent Cloud LKEAP(지식 엔진 원자 기능)전용 Rerank 구현 제공
gpustackGPUStack(프라이빗 배포)
nvidiaNVIDIA전용 Embedding / Rerank 구현
novitaNovita AI
azure_openaiAzure OpenAI추가 필드 api_version
ollama(source=local)Ollama 로컬 모델Provider 레지스트리 구성원이 아니며 ModelSourceLocal로 라우팅

모델에 provider를 명시하지 않으면 DetectProvider(baseURL)가 BaseURL 도메인 특징으로 자동 식별합니다(예: dashscope.aliyuncs.com -> aliyun, api.anthropic.com -> anthropic). 식별하지 못하면 generic으로 폴백합니다.

프로토콜 라우팅

internal/models/chat/chat.goNewRemoteChat:

go
func NewRemoteChat(config *ChatConfig) (Chat, error) {
    providerName := provider.ProviderName(config.Provider)
    if providerName == "" {
        providerName = provider.DetectProvider(config.BaseURL)
    }
    if providerName == provider.ProviderAnthropic {
        return NewAnthropicChat(config) // 독립 Messages 프로토콜
    }
    return NewRemoteAPIChat(config) // 통합 OpenAI 호환 프로토콜 + providerAdapter
}
  • Ollama(source=local): chat/ollama.go, embedding/ollama.go, vlm/ollama.gointernal/models/utils/ollamaOllamaService를 통해 로컬 Ollama에 직접 연결합니다.
  • Anthropic: chat/anthropic.go가 Messages 프로토콜을 구현합니다.
  • 나머지 원격 제공자: 모두 chat/remote_api.go의 OpenAI 호환 Chat Completions 구현을 사용합니다. 제공자 차이(thinking 인코딩, 매개변수 호환 등)는 생성 시 결정하는 providerAdapter가 처리합니다.
  • Embedding에는 더 많은 전용 구현이 있습니다. Alibaba Cloud 멀티모달(tongyi-embedding-vision-*는 DashScope 전용 엔드포인트 사용, 일반 텍스트 모델은 /compatible-mode/v1 OpenAI 호환 엔드포인트로 자동 변경), Volcengine 멀티모달, Jina, Azure OpenAI, NVIDIA, Gemini, Zhipu, WeKnoraCloud이며 나머지는 OpenAI 호환(embedding/openai.go)입니다.
  • Rerank 전용 구현: Aliyun, Zhipu, Jina, NVIDIA, WeKnoraCloud, LKEAP, Volcengine. 기본은 NewOpenAIReranker(일반 /rerank 스타일 인터페이스)입니다. 두 제공자에는 추가 어댑터가 있습니다.
    • LKEAP: Tencent Cloud RunRerank는 한 번에 최대 60개 문서, Query와 Docs 합계 2000자 이하로 제한됩니다. lkeapRerankBatches가 두 상한에 따라 자동으로 배치를 나누고 전역 인덱스를 다시 채우므로 호출자는 배치를 신경 쓰지 않아도 됩니다. 문서 하나가 자체적으로 상한을 넘으면 바로 오류를 반환하고 해당 인덱스를 알려 줍니다.
    • Volcengine: 후보 집합이 API의 단일 호출 문서 상한을 넘으면 여러 배치로 나눠 동시에 점수를 계산한 뒤 병합합니다(동시 실행 상한은 volcengineRerankMaxConcurrency 참고). 후보를 조용히 잘라 버리지 않습니다.
    • NVIDIA: API는 [0,1] 확률이 아닌 원시 logit을 반환합니다. normalizeNvidiaLogit이 수치적으로 안정적인 sigmoid로 정규화합니다(음수는 e^x/(1+e^x) 분기로 오버플로 방지). 그렇지 않으면 이 제공자에서 RerankThreshold 같은 임계값 설정이 전혀 작동하지 않습니다.
  • ASR: 모든 제공자가 OpenAI 호환 /v1/audio/transcriptions를 사용합니다(asr/asr.go: NewASR이 곧바로 NewOpenAIASR 호출).

모델 호출 체인

팩터리 함수는 실제 클라이언트 바깥에 세 데코레이터를 순서대로 적용합니다(chat.NewChat / embedding.NewEmbedder / vlm.NewVLM 참고).

go
c, err = wrapChatDebug(c, err)
c, err = wrapChatLangfuse(c, err)
// 가장 바깥 계층: 실제 제공자 왕복 호출 동안만 모델별 동시 실행 슬롯을 점유하여
// 대기 시간이 debug/langfuse 측정 시간에 포함되지 않도록 합니다.
return wrapChatConcurrency(c, config.MaxConcurrency, err)

동시 실행과 제한(limiter)

internal/models/limiter모델 ID별 분산 백그라운드 동시 실행 게이트를 제공합니다. 핵심 설계(limiter.go 패키지 주석): 공유하는 희소 자원은 모델 제공자의 요청 예산이므로 asynq 대기열 계층이 아니라 모든 작업 유형을 볼 수 있는 유일한 위치인 모델 클라이언트 계층에서 제한합니다.

  • Redis 백엔드(NewRedisLimiter): 자가 복구형 분산 세마포어입니다. 점유 슬롯마다 ZSET 구성원(고유 token)이 있고 score는 임대 만료 시간입니다. acquireScript Lua 스크립트가 원자적으로 만료 임대를 정리하고 개수를 세어 한도 안에서 진입을 허용합니다. 임대 TTL은 30s이며 점유자는 TTL/3마다 하트비트로 갱신합니다(ZSET key 자체 TTL도 갱신). 프로세스가 비정상 종료되면 임대가 자연 만료되어 회수됩니다. 어떤 백엔드 오류에서도 fail-open합니다. 제한기 장애가 모델 트래픽을 막아서는 안 됩니다.
  • Local 백엔드(NewLocalLimiter): Lite 모드(Redis 없는 단일 프로세스)의 프로세스 내 카운팅 세마포어입니다.
  • 백그라운드 작업만 제한: GateNamedN(governor.go)은 types.IsBackgroundTask(ctx)가 true일 때만 대기시킵니다(asynq worker: 요약, 질문 생성, 그래프 추출, 멀티모달 보강 등). 대화형 사용자 요청은 이 게이트로 차단하지 않습니다.
  • 한도는 모델 자체 parameters.max_concurrency를 우선 사용하고, 0이면 프로세스 수준 기본값 model.max_concurrency를 사용합니다(시스템 설정에서 런타임에 SetGlobalLimit으로 동적 갱신 가능).
  • 런타임 관측: GET /system/admin/runtime/queues(internal/handler/system.go)는 limiter.RuntimeStats()의 모델별 active / waiting / limit을 반환합니다(Redis 백엔드의 active는 클러스터 수준, waiting은 프로세스 로컬).

rerank_server_demo.py의 용도

저장소 루트의 rerank_server_demo.py자체 호스팅 Rerank 서비스의 최소 참조 구현입니다. FastAPI + HuggingFace AutoModelForSequenceClassification을 사용하며 POST /rerank를 제공합니다. 요청 본문은 {query, documents}, 응답은 {"results": [{index, document: {text}, score}]}입니다.

예시 서비스는 score 필드를 반환하므로 클라이언트 호환성을 검증하는 데 사용할 수 있습니다. RankResult.UnmarshalJSONrelevance_score를 우선 읽고 없으면 score를 읽습니다. DocumentInfo.UnmarshalJSON은 문자열과 {text} 객체를 모두 받습니다. 이 프로토콜을 따르는 프라이빗 리랭킹 서비스는 generic provider로 연결할 수 있습니다.

WeKnora v0.8.0 소스 코드 기반 한국어 번역 · MIT License