기능 구현
오늘은 마지막날이다. 이전에 연습해오던 연관검색 기능을 구현해서 넣어볼 것이다. 넣는 이유는 튜터님께서 알려주신 TRIE 알고리즘을 공부하다가 연관검색이라는 기능에도 관심이 생겨서 찾아봤었는데 이번 프로젝트에서 내가 찾고 싶은 정보와 연관된 글 목록들을 보여주는 기능이 있으면 편리할 것 같다는 생각이 들어서이다.
supabase, pgvector, Ollama와 mxbai-embed-large모델을 사용했다.
supabase는 임베딩된 벡터값을 저장하기 위해 사용했다.
Ollama를 사용하면 내 컴퓨터에서 LLM을 돌릴 수 있다! 즉, 유료 API를 사용하지 않고 직접 돌릴 수 있다.
ollama를 사용하기 위해선 expressjs로 간단하게 서버를 만들었다. 물론 expressjs를 사용해본적이 없어서 gpt씨의 도움을 받았다.
import ollama from 'ollama';
import express from 'express';
import cors from 'cors';
import pg from 'pg'
import pgvector from 'pgvector/pg';
import env from 'dotenv'
import {HttpStatusCode} from "axios";
env.config();
const client = new pg.Pool({
user: process.env.PG_USER, // PostgreSQL 사용자 이름
host: process.env.PG_HOST, // PostgreSQL 호스트 주소
database: process.env.PG_DATABASE, // PostgreSQL 데이터베이스 이름
password: process.env.PG_PASSWORD, // PostgreSQL 비밀번호
port: process.env.PG_PORT, // PostgreSQL 포트 (기본값: 5432)
});
const app = express();
const port = 8000;
app.use(express.json());
app.use(cors());
client.connect(err => {
if(err) {
console.log('DB 연결 에러 ' + err);
} else {
app.listen(port, ()=> {
console.log('서버띄움 ' + port)
});
}
})
app.post('/embeddings', async (req, res) => {
const { boardId, text } = req.body;
console.log(text);
const response = await ollama.embeddings({
model: 'mxbai-embed-large',
prompt: text },
)
const embedded = response['embedding'];
console.log(embedded);
const query = 'INSERT INTO items (board_id, title, embedding) VALUES($1, $2, $3)';
const values = [boardId, text, pgvector.toSql(embedded)];
try{
await client.query(query, values);
res.send(embedded);
} catch (e){
res.status(HttpStatusCode.BadRequest)
res.send(e);
}
});
app.put("/embeddings", async (req, res) => {
const { boardId, text } = req.body;
console.log(text);
const response = await ollama.embeddings({
model: 'mxbai-embed-large',
prompt: text },
)
const embedded = response['embedding'];
console.log(embedded);
const query = 'UPDATE items SET embedding = $1, title = $2 WHERE board_id = $3';
const values = [pgvector.toSql(embedded), text, boardId];
await client.query(query, values);
res.send(embedded);
});
app.post('/similarity', async (req, res) => {
const {text, limit} = req.body;
console.log(text);
const response = await ollama.embeddings({
model: 'mxbai-embed-large',
prompt: text },
);
console.log(text);
const embedded = response["embedding"];
console.log(embedded);
const query = `SELECT board_id as "boardId", title, 1 - (embedding <=> $1) as similarity FROM items ORDER BY similarity DESC LIMIT $2`;
const values = [pgvector.toSql(embedded), limit];
const rank = await client.query(query, values);
console.log(rank.rows);
res.send(rank.rows);
});
app.delete("/embeddings", async (req, res) => {
const { boardId} = req.body; // 클라이언트로부터 텍스트를 받음
const query = 'DELETE FROM items WHERE board_id = $1';
const values = [boardId];
await client.query(query, values);
res.sendStatus(200);
});
먼저 유저가 보드를 작성하면 스프링 서버에서 FeignClient를 이용해서 express서버에 title을 보낸 후 express 서버에서 임베딩을 하고 supabase에 저장시킨다.
여기서 임베딩이란 단어와 문장, 기타 데이터를 의미와 관계를 포착하는 숫자로 변환하는 방법입니다
글을 작성할 때 벡터값을 저장해주고, 수정하면 벡터값을 수정해주고, 삭제하면 supabase에 저장된 데이터들도 삭제 해준다.
이제 유사한 글을 가져올 때에는 위 사진 처럼 먼저 검색어(단어, 문장 등)을 express서버에 전송한다. express 서버에서는 검색어를 임베딩한 후 supabase에 쿼리를 날려준다. 쿼리는 검색어를 임베딩한 값과 supabase에 저장되어 있는 벡터값들 중 유사도가 높은 순서대로 가져오도록 했다.
const query = `SELECT board_id as "boardId", title, 1 - (embedding <=> $1) as similarity FROM items ORDER BY similarity DESC LIMIT $2`;
const values = [pgvector.toSql(embedded), limit];
const rank = await client.query(query, values);
사용한 모델이 한국어는 성능이 좋지 못해 정확한 결과는 얻지 못하지만 그래도 그래픽카드가 유사도가 가장 높게 나온 것을 볼 수 있다.!
구현하고 나니까 난이도 자체는 어렵지 않지만 한국어 정확도가 아쉬웠다ㅠㅠ
튜터님께 들은 피드백 바탕으로 북마크와 친구 서비스에 검증 로직을 추가하고 마무리했다. 4일만에 잘 끝낸 것 같다.
'부트캠프 > Dev' 카테고리의 다른 글
아웃 소싱 프로젝트 시작 (0) | 2024.09.22 |
---|---|
뉴스피드 프로젝트 회고 (2) | 2024.09.06 |
뉴스피드 프로젝트 3일차 (0) | 2024.09.05 |
뉴스피드 프로젝트 2일차 (2) | 2024.09.04 |
뉴스피드 프로젝트 1일차 (0) | 2024.09.04 |