logo
기술 블로그
logo
여기어때 피그마 플러그인 제작기
글. 한혜진(Pixie) / UX Designer부제. 피그마 플러그인 ‘여기쏙’ 개발일하면서 가장 고역인 순간을 꼽자면 아마도 비효율적인 일이 여러 번 반복될 때가 아닐까 싶어요. 게다가 한 사람이 아닌 여러 사람들이 공통적으로 겪는 일이라면 더더욱 문제이지 않을까 싶은데요. 얼마 전 피그마 플러그인 개발로 여기어때 디자이너들의 비효율을 해결한 이야기를 소개해보려 합니다.여기쏙의 시작여기어때 플러그인 개발은 어떻게 시작되었을까요? UX 센터에는 팀 내 비효율을 제거하는 Design Ops TF팀이 있어요. 작년 하반기부터 저를 비롯해 테나(Design System Owner), 제티(UX Writer)는 디자인 시스템과 Design Ops 업무를 담당하면서 효율적인 디자인 프로세스 구축에 대한 고민과 관심이 많았어요.Design Ops 업무를 진행하면서 느낀 점 중 하나는 회사마다 워크프로세스가 다르기 때문에 우리 팀의 문제점을 100% 해결해 줄 수 있는 툴이나 레퍼런스를 찾기 쉽지 않다는 것이었어요. 그래서 효율적인 업무를 위해 생산성을 높여줄 사내 툴을 직접 만들어보기로 합니다. 우리뿐만 아니라 다른 회사에서도 각 팀 프로세스에 맞는 툴을 직접 개발했다는 점이 여기쏙 개발 열정을 더욱 끌어올렸었죠.여기쏙 개발 전 열심히 찾아본 타사 레퍼런스피그마 플러그인 개발을 하려면 우선 개발자가 필요했어요. 사내에 유능하고 열정 있는 개발자분들을 찾아 제안하고, 본격적으로 프로젝트를 킥오프 하게 되었죠. 프로젝트를 시작하며 중요하게 생각했던 건 ‘즐겁게, 가볍게, 본업에 지장 없게’였어요. 이를 셀링포인트 삼아 테나(김지영/UXD)와 제티(이소연/UXW)가 사내 동호회와 프로젝트를 함께 했던 개발자분들을 모셔오게 됩니다. 총 6명의 정예(?) 멤버들이 모였고 ‘스쿼드Y(Y=여기어때의 Y를 의미)’라는 팀명도 붙이며, 신나게 개발에 박차를 가했습니다.기간제 스쿼드 Y 최정예 요원들시간 잡아먹는 더미데이터 넣기, 어떻게 해결할 수 있을까?피그마 위에서 함께 프로젝트를 일구어 가는 사람들앞서 말씀드렸듯 저희는 피그마 내에서 발생하는 문제들에 관심이 많았어요. 대부분 프로젝트의 커뮤니케이션(PO, UX, DEV)이 피그마에서 이뤄지고 있었고, 업무와 가장 밀접한 툴이기 때문에, 피그마 비효율 개선 시 임팩트가 있을 거라 생각했었어요.그중 주목할 만한 문제점은 아래와 같았죠.“디자인 리뷰나 사용자 테스트(UT)를 준비할 때, 디자인에 데이터를 채우는 과정이 번거롭다”는 것이었습니다.더미 데이터를 채울 때, 다음과 같은 과정을 겪는데요.1. 폰을 든다 > 여기어때 앱을 켠다> 원하는 정보가 있는 페이지에 들어간다 > 눈으로 보고 디자인에 따라 적는다. 2. 여기어때 웹에 접속한다 > 원하는 정보가 있는 페이지에 들어간다 >디자인에 내용을 복사 붙여 넣는다.이처럼 내용을 따라 적거나 복사 붙여넣는 과정이 예상외로 많은 시간을 들이고 있었어요. 또, 더미데이터를 직접 작성한다 해도 만들어내는데 꽤 많은 공수가 들었고, UT를 할 때 유저들이 실제 확인할 만
여기어때컴퍼니
·
하루 전
logo
코틀린을 활용한 안전한 효과 처리
안녕하세요, 전자문서 서비스의 서버를 개발하고 있는 Alan입니다.스프링(Spring)으로 서버를 개발하다 보면 데이터베이스를 사용하는 경우가 곧잘 있습니다. 이때 트랜잭션 처리를 직접 구현하기보다는 스프링의 지원을 받아 구현하는 것이 일반적인 과정입니다. 특히, 트랜잭션의 처리 방식을 정확하게 알지 못하더라도 프레임워크가 모든 과정을 대신 처리해 주기 때문에 러닝 커브가 낮다는 것이 큰 장점인데요. 그러다 보니 지나치게 프레임워크에 의존하여 안전하지 않은 코드를 무심코 작성하게 되는 경우가 종종 있는 것 같습니다.이번 글에서는 독자분들께 통상 효과적인 처리 방식이라 생각하는 스프링 프레임워크를 통한 트랜잭션 처리가 잠재적으로 가져올 수 있는 위험성을 소개하고 살펴보겠습니다. 또한, 이 문제를 해결하기 위한 방법으로 최근 스프링과 조합되어 사용되는 비중이 점차 증가하고 있는 코틀린(Kotlin)을 활용하여 잠재된 위험성을 줄이는 방식 또한 알아보도록 하겠습니다.프로그램의 효과현대의 프로그램은 격리된 환경에서 단독으로 실행되는 것이 아니라 사용자의 다양한 요구사항에 맞추어 데이터베이스에서 정보를 읽거나, 다른 프로그램과 HTTP 프로토콜을 사용한 통신으로 데이터를 주고받습니다. 이렇게 프로그램이 외부 세계와 상호 작용하는 것을 통칭 ‘효과’라고 부릅니다.아래 코드 예시와 같이, 전통적인 프로그램은 외부 세계와 상호 작용을 처리하는 컨텍스트(Context)를 함수에 직접 전달해야 했습니다.이러한 방식은 함수가 어떤 효과를 수행할 것인지 명시적으로 표현할 수 있지만, 개발자가 모든 과정을 하나하나 처리해야 한다는 단점이 있습니다. 예를 들어 데이터베이스에 데이터를 저장하려면 트랜잭션을 시작하고, 데이터를 저장하고, 트랜잭션을 커밋하는 과정을 모두 직접 처리해야 합니다. 게다가 코드의 실행 과정에서 예외가 발생할 수도 있으므로 이를 대비한 방어 로직 또한 작성해야 합니다.최근에는 개발자가 이러한 과정을 직접 구현하기보다는 프레임워크에게 처리를 일임하는 것이 각광받고 있습니다. 특히 백엔드 개발에서 주로 사용하는 스프링은 이 효과를 손쉽게 처리할 수 있도록 DI와 AOP를 지원합니다.아래 예시와 같이, 스프링 프레임워크에선 함수에 @Transactional 어노테이션을 추가하기만 하면 트랜잭션의 모든 과정을 스프링이 대신 처리하도록 구현이 가능합니다. AOP를 통한 효과를 처리하는 방법은 굉장히 유용하기는 하지만, 아쉬운 부분도 있습니다. 바로 모든 클래스와 함수에 어노테이션을 아무런 제약이 없이 설정할 수 있다는 점입니다. 이러한 유연성은 엉뚱한 곳에 효과를 부여하거나 반대로 반드시 필요한 곳에는 효과를 부여하지 못하는 부작용(Side effect)을 가져올 수 있습니다.그렇다면 효과를 안전하게 처리하려면 어떻게 해야 할까요? 전통적인 방식에서는 효과 처리에 지나친 자율성이 부여되었기 때문에 작성된 코드의 동작을 예측하기 어렵게 만들었습니다. 따라서, 이러한 생각을 스프링 프레임워크에서도 도입하여 자율성을 제약한다면, 코드의 동작을 보다 예측 가능하도록 변경할 수 있을 것입니다.지금부터는 코틀린을 활용하여 안전하게 효과를 처리하는 방법을 알아보도록 하겠습니다.코틀린에서 제약 조건을 설정하는 방법은 바로 확장 함수(Extension function)를 정의하는 것입니다.확장 함수는 함수의 이름 앞에 수신 객체 타입을 지정하여 정의할 수 있습니다. 확장 함수는 이름처럼 수신 객체 타입에 기능을 추가하는 용도로도 쓰이지만, 반대로 생각하면 확장 함수를 해당 수신 객체가 있을 때만 사용할 수 있도록 제약하는 방식으로도 사용할 수도 있습니다.save 함수에 DatabaseContext라는 수신 객체 타입을 설정하면 컨텍스트 없이는 단독으로는 save 함수를 호출할 수 없습니다.이제 코틀린의 확장 함수를 사용해서 스프링 예제 코드를 개선해 보도록 하겠습니다. 코드의 요구사항은 아래와 같습니다.• 상품을 삭제하는 프로그램을 개발한다.• 상품을 삭제하는 함수는 반드시 트랜잭션 범위 안에서만 호출해야 한다.스프링에서는 함수를 트랜잭션 범위 안에서만 호출할 수 있도록 만들고 싶다면 아래와 같이 @Transactional 어노테이션을 추가하고 MANDATORY 옵션을 설정하기만 하면 됩니다.그런데 만약 @Transactional 어노테이션이 누락된 채로 코드가 작성되었다면 어떤 위험이 있을까요? AOP 방식의 트랜잭션은 컴파일 타임에는 호출 관계에 대한 문법 에러를 감지할 방법이 없습니다. 따라서 런타임에 함수를 호출하게 되면 서버에서 에러가 발생하게 됩니다.그렇다면 이 문제를 해결할 수 있는 근본적인 해결책은 무엇일까요? 가장 좋은 방법은 요구 사항 그대로 트랜잭션이 없는 경우, 컴파일 타임에 에러가 감지되도록 하여 개발자의 실수를 방지하는 것입니다. 이때, 아래 예시와 같이 코틀린을 사용한다면 delete 함수에 트랜잭션을 처리해 줄 특별한 컨텍스트를 지정하여 이 목표를 쉽게 달성할 수 있습니다.개선된 코드에서는 delete 함수에 @Transactional을 지정하는 대신에 DatabaseContext를 수신 객체 타입으로 지정하였습니다. 지금부터는 확장 함수의 특성으로 인해 DatabaseContext와 ProductComponent가 모두 주입된 영역에서만 delete 함수를 호출할 수 있습니다. 즉, 잘못된 코드를 억지로 사용하고 싶어도 컴파일 과정에서 문법 에러가 감지되어 컴파일이 실패하게 됩니다. 이는 기존에는 런타임 시점에서야 감지할 수 있었던 에러가 원천적으로 차단될 수 있다는 것을 시사하며, 효과를 안전하게 처리하는 방식에 한 발짝 가까워졌음을 의미합니다.고차 함수로 효과 함수 전달코틀린은 함수를 인자로 받거나 리턴할 수 있는 고차 함수를 지원합니다. 코틀린의 고차 함수를 적용한 예시는 아래와 같습니다.기존에 살펴보았던 delete 함수는 이제 상품을 직접 삭제하는 것이 아니라, 상품을 삭제하는 함수를 리턴하는 고차 함수로 작성되었습니다. 물론 리턴되는 함수에도 DatabaseContext가 수신 객체 타입으로 지정할 수 있기 때문에 제약 사항도 잘 지켜지고 있습니다.효과 함
카카오
·
2일 전
logo
GPT에게 1-100 사이의 숫자 중 하나를 고르라면 몇을 말할까요?
GPT에게 1-100 사이의 숫자 중 하나를 고르라면 몇을 말할까요? 그 결과가 고르게 분포할까요?이 흥미로운 실험을 한 사람이 있습니다.답은 "42"일 확률이 가장 높습니다.복잡성이 증가함에 따라 학습데이터와 관련된 편향 문제도 함께 증가합니다.출처가 되는 이 글에서는 GPT의 내재된 편향을 형성하는 다양한 요소를 탐구합니다.GPT-3.5-turbo 모델을 사용하여 1부터 100 사이의 정수를 선택하도록 1000번 요청한 결과, 특정 숫자에 대한 편향이 드러났습니다.바로 42가 가장 많이 선택된 것인데요!42는 “The Hitchhiker’s Guide to the Galaxy”에서 생명, 우주, 그리고 모든 것의 해답으로 유명해진 숫자라고 합니다.가짜연구소 커뮤니티에서 얘기를 나눴었는데, 루이스 캐럴의 이상한 나라의 앨리스에서도 많이 나오고,포티투닷이라는 스타트업, 42서울이라는 교육단체 등 42이라는 숫자를 주변에서도 꽤 발견할 수 있습니다.또 흥미롭게도 와일드카드(*)의 아스키 코드 또한 42라고 합니다. (최동훈님 감사해요!)Temperature 설정은 GPT의 응답 예측 가능성을 조절합니다.낮은 Temperature에서는 더 일관된 답변을, 높은 Temperature에서는 더 다양한 답변을 생성합니다.테스트 결과, 낮은 Temperature에서는 특정 숫자를 강하게 선호하는/편향된 선택을 유도하는 반면, 높은 Temperature에서는 선택의 다양성이 증가했습니다.시스템의 무작위성을 측정하는 데 엔트로피를 사용할 수 있습니다.Temperature 설정에 따른 GPT의 응답에서 엔트로피의 변화. 최저 온도 값에서는 엔트로피가 0이 되어 완전히 결정론적 출력을 나타냅니다.모든 선택이 단일 숫자와 일치할 때, 엔트로피는 0이 됩니다. 반대로, 선택이 완전히 무작위일 때 엔트로피는 1의 정규화된 값에 도달합니다.따라서, GPT의 "편향"은 1에서 엔트로피를 뺀 값으로 볼 수 있습니다.3. 편향에 때한 이해는 인공지능에 대한 이해일까? 사람에 대한 이해일까?이러한 편향을 이해하고 인식하는 것이 AI를 효과적으로 활용하는 데 중요합니다.그런데 이런 편향은 사실 학습데이터에 큰 영향을 받습니다.의도하고 만든 편향된 데이터가 아니라고 한다면, 사실 이러한 편향은 사람의 지식과 문화, 그 삶을 반영하고 있다고 생각할 수 있지 않을까요?마치 42라는 숫자가 인공지능에게 중요한 수가 아니라, 사람에게 중요한 수가 되는 것이 아닐까 생각됩니다.좀 더 상세한 내용이 궁금하시다면 원글을 확인해보세요!
SK텔레콤
·
2일 전
logo
Nuxt.js로 빠르게 서비스 런칭하기
안녕하세요. 오랜만에 인사드립니다.올 상반기에 에이닷 앱의 웹 뷰 서비스를 새로 구축하는 업무를 맡게 되었습니다.Nuxt.js 프레임웍(이하 Nuxt) 기반으로 빠르게 서비스를 출시 할 수 있었는데 그 이야기를 해보려고 합니다.에이닷 앱 웹 뷰는 2018년도 기술로 구축 된 레거시 프로젝트가 존재합니다.6년이라는 시간이 흐르는 동안 Front-End 진형의 기술은 크게 발전했습니다.기술 부채가 많은 레거시 프로젝트 위에서 서비스를 확장하려면 여러 불편함이 존재했는데, 이는 최신 기술을 사용하면 대부분 해결될 문제였습니다.바꾸고 싶다는 생각은 여러 번 했지만 시간과 자원이 많이 들어가는 일이라 결정이 필요했습니다.담당님의 지원으로 프로젝트 세팅, 빌드/배포, 서버 구축까지 새로 할 수 있게 됐습니다.현재 가장 인기가 많은 라이브러리는 React.js(이하 React)입니다. 그런데 왜 Vue.js(이하 Vue)의 Nuxt를 선택했을까요?가장 큰 이유는 "러닝 커브(Learning curve)가 낮다"를 이유입니다.저는 React로 4년, Vue로 4년 개발했습니다. React를 먼저 배우고 나중에 Vue를 배웠습니다. React의 개념이 있는 상태에서 Vue를 배우는 건 어렵지 않았습니다.양방향 바인딩의 개념을 이해하고 잘 사용할수 있으면 오히려 편하다는 느낌을 받았습니다.신규 프로젝트 세팅과 신규 서비스 출시 기간이 짧지 않은 상태에서 러닝 커브가 높은 React보다 낮은 Vue를 선택하는 게 맞다고 판단했습니다.또 React는 단방향 바인딩만 가능하기 때문에 부모 컴포넌트의 상태를 바꾸려면 handler를 구현해서 자식 컴포넌트에 props로 내려 줘야 하는데 이것은 props drilling의 고통을 가중합니다.Vue의 경우 양방향 바인딩을 지원하기 때문에 이를 깔끔하게 처리 할수 있다는 장점도 크게 다가왔습니다.React 생태계만큼 Vue도 디버깅 도구, 빠른 업데이트, Nuxt를 지원하고 있어 서비스를 운영하는데 문제가 없다고 판단했습니다.기존 레거시는 jQuery 라이브러리가 코어 모듈이었습니다.jQuery의 경험이 있는 분은 알겠지만, HTML 요소의 변경을 주려면 요소를 선택하고 제공하는 속성에 접근해서 값을 하나하나 변경해야 합니다. 정말 귀찮은 일입니다.데이터 기반의 라이브러리를 사용하면 데이터 변경 시 라이브러리에서 알아서 변경해 주기 때문에 불필요한 DOM 요소 조작이 필요 없습니다.아래 예는 Vue 방식입니다.아주 단순한 코드라 별 차이가 없다고 느낄 수 있으나 DOM 요소를 찾는 일을 100번 반복한다고 상상하면 크게 다가오실 겁니다.이 장점은 Nuxt의 장점이라기보다 Vue의 장점이지만 레거시 프로젝트와 비교해서 가장 큰 장점이라 먼저 이야기합니다.프레임웍의 장점은 수많은 논의 끝에 정규화된 규칙이 존재하고 사용자는 그 규칙에 맞게 세팅하면 자동으로 해결되는 게 많다는 점입니다.프레임웍을 사용하지 않고 프로젝트를 구성하려면 프로젝트의 구조, 사용할 코어 라이브러리, 폴더 이름 하나까지도 논의가 이뤄져야 합니다.프레임웍을 도입함으로써 결정의 시간을 줄일 수 있었습니다.빌드 설정은 자주 하는 게 아니다 보니 늘 복잡하고 헷갈립니다.Nuxt는 대부분의 설정을 알아서 해줍니다. 몇 가지 필요한 부분만 문서를 통해 이해하고 추가하면 되기 때문에 간편합니다.복잡한 Webpack 문서를 읽을 필요가 없습니다.Nuxt에서 제공하는 유틸리티 함수들이 있습니다.서버의 API 호출을 하려면 대게는 라이브러리를 사용합니다. Axios가 대표적입니다.Nuxt는 Ajax 호출을 할 수 있는 내장 유틸리티 함수를 제공합니다. fetch 함수인데 사용 방법이 간단합니다.개발하다 보면 필요한 모듈들을 import 해서 상단이 지저분해지는 경험을 했을겁니다.순서도 개발자마다 스타일이 달라서 논쟁이 되죠.nuxt는 특정 폴더의 파일들을 알아서 import 해주기 때문에 신경 쓰지 않고 개발할 수 있습니다.Nuxt로 프로젝트를 기본 틀을 설정한 후, 실무에서 필요한 몇 가지를 추가했습니다.Nuxt에서 제공하는 디렉터리 외에 필요한 디렉리가 있어서 추가했습니다.• None constants : 상숫값을 등록하는 디렉터리입니다.• None dto : DTO(Data Transfer Object)가 모여 있는 디렉터리입니다.• None type : 타입스크립트의 타입이 모여 있는 디렉터리입니다.• None stores : 서버 API를 호출하고 pinia를 사용한 상태 관리를 위한 디렉터리입니다.기존 레거시 프로젝트의 문제점 중의 하나였던 부분이 코딩 컨벤션이 제각각 달랐다는 점입니다.IDE 설정을 하지 않고, 코드 리뷰도 하지 않으면 코딩 컨벤션을 지키게 할 방법이 없기 때문에 이를 강제할 수 있는 수단이 필요했습니다.husky를 사용하여 git commit을 할 때 자동으로 prettier 검사가 되고 자동으로 수정 됩니다. prettier 맞게 수정해야 commit을 할수 있도록 강제했습니다.gitlab runner를 활용해서 MR(Merge Request)을 올리면 자동으로 eslint, test가 동작하도록 구성했습니다. 모두 pass 해야 MR을 Merge 할 수 있도록 강제했습니다.nuxt.config.ts 는 Nuxt의 설정을 세팅을 변경할 수 있는 파일입니다. 서비스에 적용하면서 몇 가지 추가한 내용을 남깁니다.아래와 같이 app 하위에 head에 들어갈 정보를 수정할 수 있습니다.server side rendering이 기본으로 true가 되어 있기 때문에 ssr 옵션을 false로 변경해 줘야 정적 파일로 빌드됩니다.서버 환경 구축에 따라 옵션을 설정하면 됩니다.신규 프로젝트를 구성하는데 어떤 라이브러리와 프래임웍을 사용할지 결정하는 것은 쉽지 않습니다.많은 서비스가 운영되고 있다는 사실을 알고 있지만, 실제 내 서비스에 도입할 때는 상황에 따라 고려해야 하는 부분이 다를 것입니다.이 글이 새로운 프로젝트를 구성하는 데 의사결정을 하는데 도움이 되었으면 좋겠습니다.
SK텔레콤
·
2일 전
logo
토스뱅크가 AI로 보안과 효율도 챙기는 방법
은행 창구에서 본인확인을 위해 뭘 요구할까요? 바로, 신분증입니다! 비대면 은행인 토스뱅크에서도 서비스를 사용하려면 신분증이 가장 먼저 필요해요. 비대면 은행은 스마트폰으로 촬영된 신분증 이미지를 확인해서 위조 신분증인지 아닌지, 혹은 주요한 정보들이 잘 보이는지 확인할 의무가 있는데요. 이 작업은 사람이 모든 건을 일일이 확인하며 판단해야 해요.토스뱅크는 다양한 곳에서 AI 기술을 활용하고 있어요. 오늘은 신분증 검증 과정에서 ML 모델로 어떻게 수기 작업을 최소화했는지 알려드릴게요.신분증을 제출하면, 신분증을 검증한 다음에 계좌가 개설되는 것이 일반적이에요. 하지만 토스뱅크에서는 빠르고 편리한 고객 경험을 위해 계좌를 먼저 개설하고 그 이후에 신분증을 검사하는 사후 검증 방식을 선택했어요. 하지만 이 방식에는 신분증 위조나 금융 범죄가 일어날 수 있어요. 토스뱅크는 이런 ML 모델을 적극적으로 활용하며 이런 사고를 방지하고 있어요. 사고가 발생하기 전에 미리 이상 케이스를 탐지해서 거래를 막는 것이죠.앞서 SLASH 23에서 AI와 함께 가짜 신분증 찾아내기 주제로 발표한 적이 있는데요. ‘가짜 신분증 찾기’는 토스뱅크의 신분증 검증에 일부일 뿐이에요. 가짜 신분증을 걸러내는 것뿐만 아니라 신분증의 개인 정보가 잘 보이는지, 증명사진에서 얼굴이 잘 식별되는지 등의 기준도 함께 확인하고 있어요.이전에는 사람이 신분증을 종합적으로 검증했어요. 하지만 토스뱅크의 고객이 늘어나면서 신분증의 양도 늘어났고, 검증에 소요되는 시간과 비용을 줄이기 위한 방법이 필요했어요. 이에 따라 가짜 신분증을 찾아내는 ML 모델과 더불어, 신분증의 개인 정보를 추출하는 모델, 얼굴을 인식하는 모델 등을 추가했어요. 이렇게 다양한 모델을 활용해 종합적으로 신분증으로 검증하는 “신분증 사본 품질 검사 서비스”를 실제 토스뱅크에 도입하기 시작했고요.오늘은 해당 서비스가 토스뱅크에 어떻게 활용되고 있는지 알려드릴게요.고객이 제출한 신분증이 토스뱅크의 검증 기준에 적합하면 승인되고, 결격 사유가 있다면 반려돼요. 신분증이 반려된 고객은 다시 신분증 사진을 제출할 수 있도록 도와드려요. 신분증 승인은 아직 사람이 최종적으로 검증하지만, 사람이 보는 신분증의 양을 줄이기 위해 신분증 “반려” 과정을 자동화했어요. “신분증 사본 품질 검사 서비스” 모델로 사용자가 제출한 신분증이 토스뱅크의 검증 기준을 만족하는지 확인한 것이죠.해당 모델에는 이미지 분류 모델, 객체 탐지 모델 등 다양한 모델이 각 검증 기준에 대한 점수를 0과 1 사이의 값을 계산해요. 0에 가까울수록 정상 신분증에 가깝다고 판단한 것이고, 1에 가까울수록 반려해야 할 신분증에 가깝다고 판단한 거죠. 0과 1 사이에 임계값(thershold)을 설정하고, 모델이 예측한 결과가 해당 임계값을 넘으면 신분증을 반려하는 시스템입니다.하지만 임계값을 어떻게 설정해야 될까요?먼저 모델을 평가하는 방법을 설명드릴게요. 모델이 기준에 부합하지 않은 신분증 반려하지 않은 경우를 False Negative라고 해요. Fa
비바리퍼블리카
·
2일 전
logo
Spring AI를 적용해보자!
요새 ChatGPT 등 생성형 AI를 서비스에 접목시키는 기업들이 늘어나고 있고, 이러한 차원에서 생성형 AI를 프로젝트에 넣어보고자 하는 분들이 많은 기류를 느끼고 있습니다.스프링에서는 아래의 영상을 통해 자체 기술인 Spring AI를 소개하는 영상을 만들기도 하였죠.나온 지 얼마 되지 않은 기술이다 보니, 아직 이 기술을 블로그에 기록하신 분이 많이 없는 것 같아 프로젝트 상황에서 적용한 과정을 공유하고자 합니다.스프링 공식 문서에 따르면, Spring AI는 다음과 같은 특징을 가지고 있습니다.저희가 API를 이용하여 인공지능 서비스를 이용하기 위해서는 RestTemplate, WebClient 등 HTTP 연결을 직접 지정해야 했습니다.하지만 Spring AI를 이용하면, 스프링의 추상화를 이용하여 쉽게 요청을 보낼 수 있게 됩니다. 이러한 점에서 도입해보고 싶은 생각이 들었습니다.작성했던 다른 글들처럼, 이번에도 스프링의 공식 문서를 참고하여 적용해 보겠습니다.먼저, build.gradle 안에 있는 repositories를 아래 사진과 같이 작성해야 합니다.Spring AI는 아직 계속 개발 중이라 마일스톤과 스냅샷에 관리되어 있어 그런 것 같습니다.그다음으로는 dependencies에 (OpenAI를 이용할 경우) org.springframework.ai:spring-ai-openai 등의 의존성을 등록시켜야 합니다.저는 버전을 구체적으로 명시한 방법 (첫 번째 줄)을 사용하였습니다.그럼 이제 ChatGPT와 같은 채팅 AI를 호출할 수 있는 관련 코드가 어떤 게 있는지 알아봐야겠죠.공식 문서와 홍보 영상을 참고하면, ChatClient라는 함수형 인터페이스를 통해 이끌어낼 수 있음을 보실 수 있습니다.공식 문서에 있는 ChatClient와 StreamingChatClient 인터페이스 코드 - 번역이 좀 이상한데, 아래 설명을 참고해주세요!• None ChatResponse 등에 대한 복잡함을 줄이기 위해 사용할 수 있습니다.• None 예시로 "문자열을 프롬프트로 보내고, 문자열로 응답한 값만 받고 싶은 경우" 사용하면 좋습니다.• None 실제 내부 코드를 보면, 아래의 ChatResponse call(Prompt prompt) 메서드를 호출합니다.• None Prompt 타입으로 요청을 보내고, ChatResponse 타입으로 응답을 받고 싶은 경우 사용할 수 있습니다.• None 밑에 후술 할 추가적인 정보를 이용할 수 있어, 공식 문서에서는 실제 애플리케이션 상황에서 더 자주 쓰인다고 합니다.StreamingChatClient는 Reactive Flux 방식으로 요청과 응답을 주고받을 수 있는 것 같은데,이 부분에 대해서는 잘 알고 있지 않은 개념이라 본 글에서는 ChatClient를 기반으로 설명드리겠습니다.채팅 흐름을 이해하기 위해서는 ChatClient 이외에도 Prompt와 ChatResponse에 대해 이해하셔야 합니다.Prompt는 메시지 (Message)의 리스트와 ChatOptions (채팅 모델 옵션)을 캡슐화 한 클래스입니다.1️⃣ 모든 요청과 함께 전달되는 채팅 옵션으로, 요청 시 시스템에 전달되며 시작 옵션을 덮어쓸 수 있습니다. (있는 경우)2️⃣ instructions (명령들)은 채팅 완성 및 임베딩 모델의 텍스트 목록, CV 모델의 오디오 또는 이미지/비디오 등이 될 수 있습니다.메시지 (Message)는 간단히 말해서, 저희가 일상적으로 사용하는 프롬프트 메시지라고 생각하시면 됩니다. 후술 하겠지만, 응답 메시지를 받을 때에도 이 메시지가 사용됩니다.Message의 구현체로 AbstractMessage가 있고, 이 AbstractMessage를 상속받은 UserMessage를 ChatClient에서 사용합니다.ChatClient에서는 Prompt를 GPT AI의 API에 맞게 입력 명령들을 변환하고, 채팅 옵션들을 병합한 뒤 GPT에 API 요청을 보내게 됩니다.그리고 이 응답을 ChatResponse로 받을 수 있게 합니다.3️⃣ ChatClient에서는 입력 명령들을 모델 입력 형식에 맞게 변환하는 과정을 거칩니다.4️⃣ 실행 시에 비어 있지 않은 옵션들은 시작 시 옵션들을 덮어씁니다.5️⃣ 채팅 옵션은 ChatClient 초기화 중에 시작 시 설정됩니다. (모델마다 선택적으로 구현)6️⃣ AI 모델이 반환한 응답을 ModelRequest, ModelResponse로 변환합니다.ChatResponse는 채팅 응답에 대한 메타데이터 (ChatResponseMetadata), Generation을 가지는 응답 객체입니다.ChatResponseMetadata는 AI 모델에 대한 RateLimit (API 호출 제한량), Usage (사용량) 등을 확인할 수 있습니다.이 인터페이스를 구현한 OpenAiChatResponseMetadata를 보면 더 구체적으로 원리를 이해하실 수 있습니다.이번에는 Generation에 대해 알아보겠습니다.Generation 클래스는 모델 결과를 확장하여 보조 메시지 (AssistantMessage) 응답과 관련된 메타데이터를 나타냅니다.위의 설명을 기억하고 계신 분들이라면, 위의 메시지 (Message)의 하위 타입으로 UserMessage, AssistantMessage가 함께 있는 것을 기억하고 계실 겁니다.그리고 함께 띄워둔 MessageType을 통해, 메시지의 타입 중에는 USER와 ASSISTANT가 있었음도 아실 겁니다.GPT에 요청을 보낼 때는 ChatClient에서 UserMessage를 사용하였고, 응답을 받을 때는 AssistantMessage가 Generation 안에 있음을 보았습니다.즉, 이를 통해 다음 사실을 추론할 수 있습니다.• None 요청을 보낼 때는 Message의 타입이 USER이다. (사람이 보낸 것이므로)• None 응답을 받을 때 AI 모델에서 전달한 Message의 타입은 ASSISTANT이다.다시 복기하기 위해 ChatClient의 내부 코드를 다시 보여드리겠습니다!그렇다면 String call(String message)에서 반환 줄이 왜
SK텔레콤
·
3일 전
기술 블로그 더 보기
Copyright © 2024. Codenary All Rights Reserved.