실행명령어

podman run -d -p 8000:7997 docker.io/michaelf34/infinity --model-id sentence-transformers/all-mpnet-base-v2

임베딩 모델 추가 완료
This commit is contained in:
mskim 2025-12-15 14:12:30 +09:00
parent d01de88078
commit e1937aea60
5 changed files with 139 additions and 14 deletions

View File

@ -559,10 +559,10 @@ MCP 서버를 초기화하고 서버 정보 및 지원 기능을 반환합니다
"result": {
"contents": [
{
"uri": "memory://recent",
"mimeType": "application/json",
"uri": "memory://recent",
"mimeType": "application/json",
"text": "{\"message\": \"Resource content for memory://recent\"}"
}
}
]
}
}

View File

@ -180,7 +180,7 @@ public class McpController {
toolArguments = params.getArguments();
}
yield mcpService.callTool(toolName, toolArguments);
yield mcpService.callTool(toolName, toolArguments);
}
case "resources/list" -> mcpService.listResources();
case "resources/read" -> {

View File

@ -0,0 +1,104 @@
package com.pandol365.dewey.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
/**
* 데이터베이스 초기화
* 프로젝트 시작 pgvector 확장 테이블 자동 생성
*/
@Slf4j
@Component
@Order(1) // 다른 초기화보다 먼저 실행
@RequiredArgsConstructor
public class DatabaseInitializer implements CommandLineRunner {
private final JdbcTemplate jdbcTemplate;
@Override
public void run(String... args) {
try {
log.info("=== 데이터베이스 초기화 시작 ===");
// 1. pgvector 확장 확인 생성
initializePgVectorExtension();
// 2. memory_embeddings 테이블 생성
createMemoryEmbeddingsTable();
// 3. 인덱스 생성
createVectorIndex();
log.info("=== 데이터베이스 초기화 완료 ===");
} catch (Exception e) {
log.error("데이터베이스 초기화 실패: {}", e.getMessage(), e);
// 초기화 실패해도 애플리케이션은 계속 실행 (이미 존재하는 경우 )
}
}
/**
* pgvector 확장 생성
*/
private void initializePgVectorExtension() {
try {
log.info("pgvector 확장 확인 중...");
jdbcTemplate.execute("CREATE EXTENSION IF NOT EXISTS vector");
log.info("pgvector 확장 확인 완료");
} catch (Exception e) {
log.warn("pgvector 확장 생성 실패 (이미 존재할 수 있음): {}", e.getMessage());
}
}
/**
* memory_embeddings 테이블 생성
*/
private void createMemoryEmbeddingsTable() {
try {
log.info("memory_embeddings 테이블 생성 중...");
String sql = """
CREATE TABLE IF NOT EXISTS memory_embeddings (
id BIGSERIAL PRIMARY KEY,
memory_id BIGINT NULL,
user_id VARCHAR(255),
memory_text TEXT NOT NULL,
importance INT DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
embedding VECTOR(768) NOT NULL
)
""";
jdbcTemplate.execute(sql);
log.info("memory_embeddings 테이블 생성 완료");
} catch (Exception e) {
log.warn("memory_embeddings 테이블 생성 실패 (이미 존재할 수 있음): {}", e.getMessage());
}
}
/**
* 벡터 인덱스 생성 (cosine similarity)
*/
private void createVectorIndex() {
try {
log.info("벡터 인덱스 생성 중...");
// 기존 인덱스가 있으면 삭제 재생성 (IF NOT EXISTS는 ivfflat에서 지원 )
String dropIndexSql = "DROP INDEX IF EXISTS memory_embeddings_embedding_idx";
jdbcTemplate.execute(dropIndexSql);
// ivfflat 인덱스 생성 (lists=100은 데이터량에 따라 조정 필요)
String createIndexSql = """
CREATE INDEX memory_embeddings_embedding_idx
ON memory_embeddings USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100)
""";
jdbcTemplate.execute(createIndexSql);
log.info("벡터 인덱스 생성 완료");
} catch (Exception e) {
log.warn("벡터 인덱스 생성 실패 (이미 존재할 수 있음): {}", e.getMessage());
}
}
}

View File

@ -24,12 +24,15 @@ public class EmbeddingClient {
@Value("${embedding.api.base-url:http://localhost:8000}")
private String embeddingBaseUrl;
@Value("${embedding.api.path:/embed}")
@Value("${embedding.api.path:/embeddings}")
private String embeddingPath;
@Value("${embedding.api.model:sentence-transformers/all-mpnet-base-v2}")
private String embeddingModel;
/**
* 텍스트 임베딩 생성
* 기대 응답 형식: { "embedding": [float, float, ...] }
* Infinity API 형식: 요청 { "model": "...", "input": "..." }, 응답 { "data": [{"embedding": [...]}] }
*/
public float[] embed(String text) {
try {
@ -37,21 +40,38 @@ public class EmbeddingClient {
.scheme(extractScheme(embeddingBaseUrl))
.host(extractHost(embeddingBaseUrl))
.port(extractPort(embeddingBaseUrl))
.path(embeddingPath == null ? "/embed" : embeddingPath)
.path(embeddingPath == null ? "/embeddings" : embeddingPath)
.toUriString();
// Infinity API 요청 형식: {"model": "...", "input": "..."}
Map<String, Object> request = Map.of(
"model", embeddingModel != null ? embeddingModel : "sentence-transformers/all-mpnet-base-v2",
"input", text
);
@SuppressWarnings("unchecked")
Map<String, Object> resp = restTemplate.postForObject(
url,
Map.of("text", text),
request,
Map.class
);
if (resp == null || !resp.containsKey("embedding")) {
throw new IllegalStateException("embedding field missing in response");
if (resp == null || !resp.containsKey("data")) {
throw new IllegalStateException("data field missing in response");
}
Object embObj = resp.get("embedding");
Object dataObj = resp.get("data");
if (!(dataObj instanceof List<?> dataList) || dataList.isEmpty()) {
throw new IllegalStateException("data is not a non-empty list");
}
// data[0].embedding 추출
Object firstItem = dataList.get(0);
if (!(firstItem instanceof Map<?, ?> firstMap)) {
throw new IllegalStateException("data[0] is not a map");
}
Object embObj = firstMap.get("embedding");
if (!(embObj instanceof List<?> list)) {
throw new IllegalStateException("embedding is not a list");
}

View File

@ -17,6 +17,7 @@ spring.data.redis.host=localhost
spring.data.redis.port=6379
# Embedding service
# all-mpnet-base-v2 768d embedding endpoint
embedding.api.base-url=http://localhost:8000
embedding.api.path=/embed
# all-mpnet-base-v2 768d embedding endpoint (infinity)
embedding.api.base-url=http://192.168.100.220:8000
embedding.api.path=/embeddings
embedding.api.model=sentence-transformers/all-mpnet-base-v2