실행명령어
podman run -d -p 8000:7997 docker.io/michaelf34/infinity --model-id sentence-transformers/all-mpnet-base-v2 임베딩 모델 추가 완료
This commit is contained in:
parent
d01de88078
commit
e1937aea60
|
|
@ -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\"}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" -> {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue