[Spring-AI] 01. 기본 컨셉
- -
이번 포스트에서는 Spring AI의 기본 컨셉에 대해 살펴보자. AI 내용이니까 기본 내용은 AI에게 요약시키고 필요한 부분만 첨언해본다.ㅎ
Spring AI의 개요
개요
Spring AI는 애플리케이션에 인공지능을 쉽게 통합할 수 있도록 설계된 모듈형 프레임워크이다. 이를 통해 애플리케이션 개발의 용이성을 높이고 기업 데이터 및 API와 다양한 AI 모델을 효율적으로 연결하는데 중점을 둔다.
이 프로젝트는 LangChain이나 LlamaIndex등 유명한 Python 프로젝트에서 영감을 얻었지만 이들을 직접 자바로 포팅한 것은 아니고 추후 생성형 AI의 활용이 다양한 언어에서 활발히 될 것이라는 믿음 하에서 진행되었다고 한다.
Spring AI는 엔터프라이즈 애플리케이션의 데이터와 API를 생성형 API와 연결하는 데 최적화된 구조를 제공한다. 이를 위해 다양한 AI 서비스 제공자(Open AI, Anthropic, Google, Microsoft, Amazon)를 아우르는 포터블 API를 제공하며 chat, text-image 변환, 임베딩 모델 등을 지원하며 동기식 및 스트리밍 API를 지원한다.
일단은 생성형 AI를 다루면서 만나는 용어들을 정리하고 가보자.
AI 컨셉
모델
AI 모델은 정보를 처리하고 생성하도록 설계된 알고리즘으로, 종종 인간의 인지 기능을 모방한다. 이러한 모델은 대규모 데이터 세트에서 패턴과 통찰력을 학습하여 산업 전반의 다양한 애플리케이션을 향상시키는 예측, 텍스트, 이미지 또는 기타 출력을 생성할 수 있다.
다른 모델 대비 GPT의 차별화 지점은 사전 훈련된 특성이다. GPT(Chat Generative Pre-trained Transformer)의 "P"에서 알 수 있듯이 이 사전 훈련 기능은 AI를 광범위한 머신 러닝 또는 모델 훈련 배경이 필요하지 않은 범용 개발자 도구로 변환 시킨다. 그냥 쓰면 된다.
Prompts
ChatGPT에 익숙한 사람에게 프롬프트는 단순히 대화 상자에 입력된 텍스트로 보일 수 있지만, 그 의미는 훨씬 더 깊다. 많은 AI 모델에서 프롬프트의 텍스트는 단순한 문자열이 아니다.
ChatGPT의 API는 여러 개의 텍스트 입력을 포함하는 프롬프트를 사용하며, 각 입력은 특정 역할(role)을 가진다. 예를 들어, 시스템 역할은 모델의 행동 방식을 지시하고 상호작용의 맥락을 설정하며, 사용자 역할은 일반적으로 사용자의 입력이다.
효과적인 프롬프트를 만드는 것은 예술과 과학의 조화다. ChatGPT는 인간 간의 대화를 위해 설계되었으며, 이는 SQL과 같은 쿼리를 사용하는 것과는 큰 차별점이 있다. AI 모델과의 상호작용은 마치 다른 사람과 대화하는 것처럼 이루어져야 한다.
이러한 상호작용 방식의 중요성 때문에 "프롬프트 엔지니어링(Prompt Engineering)"이라는 새로운 분야가 생겨났다. 효과적인 프롬프트를 개선하기 위한 다양한 기술이 개발되고 있으며, 프롬프트를 만드는 데 시간을 투자하는 것이 결과적으로 출력의 질을 크게 향상시킬 수 있다.
프롬프트 공유는 공동체적인 관행이 되었고, 이 주제에 대한 활발한 학술 연구가 진행되고 있다. 예를 들어, 효과적인 프롬프트를 만드는 것이 직관적이지 않을 수 있다는 점을 보여주는 연구 결과가 있다. 최근 연구 논문에서는 가장 효과적인 프롬프트 중 하나가 "심호흡을 하고 단계별로 작업하세요."라는 문구로 시작된다는 사실을 발견했다. 이는 언어의 중요성을 잘 보여준다. 우리는 ChatGPT 3.5와 같은 이전 기술의 최적 사용법을 아직 완전히 이해하지 못하고 있으며, 새로운 버전이 개발되고 있다는 점에서 그 중요성이 더욱 강조된다.
Prompt Templates
효과적인 프롬프트를 생성하는 것은 요청의 맥락을 설정하고, 요청의 일부를 사용자의 입력에 맞는 값으로 대체하는 과정을 포함한다.
이 과정은 프롬프트 생성 및 관리를 위해 전통적인 텍스트 기반 템플릿 엔진을 사용한다. Spring AI는 이를 위해 OSS 라이브러리인 StringTemplate을 활용한다.
예를 들어, 다음과 같은 간단한 프롬프트 템플릿을 고려해보자:
"Tell me a {adjective} joke about {content}."
Spring AI에서 프롬프트 템플릿은 Spring MVC 아키텍처의 "View"에 비유될 수 있다. 일반적으로 java.util.Map 타입의 모델 객체가 제공되어 템플릿 내의 자리 표시자를 채운다. "렌더링(rendered)"된 문자열은 AI 모델에 제공되는 프롬프트의 내용이 된다.
모델에 전송되는 프롬프트의 특정 데이터 형식에는 상당한 변동성이 있다. 처음에는 단순한 문자열로 시작했지만, 프롬프트는 이제 여러 메시지를 포함하게 되었고, 각 메시지의 문자열은 모델의 각기 다른 역할을 나타낸다.
Embeddings
임베딩(Embeddings)은 텍스트, 이미지 또는 비디오의 수치적 표현으로, 입력 간의 관계를 포착한다.
임베딩은 텍스트, 이미지, 비디오를 부동 소수점 숫자의 배열인 벡터로 변환함으로써 작동한다. 이 벡터들은 텍스트, 이미지, 비디오의 의미를 포착하도록 설계되었다. 임베딩 배열의 길이를 벡터의 차원(dimensionality)이라고 한다.
두 개의 텍스트 조각 간의 벡터 표현 사이의 수치적 거리를 계산함으로써, 애플리케이션은 임베딩 벡터를 생성하는 데 사용된 객체 간의 유사성을 판단할 수 있다.
Java 개발자가 AI를 탐색할 때, 이러한 벡터 표현 뒤에 있는 복잡한 수학 이론이나 구체적인 구현을 이해할 필요는 없다. AI 시스템 내에서 임베딩의 역할과 기능에 대한 기본적인 이해만 하면 충분하다. 특히 AI 기능을 애플리케이션에 통합할 때 그렇다.
임베딩은 Retrieval Augmented Generation (RAG) 패턴과 같은 실용적인 애플리케이션에서 특히 중요하다. 임베딩은 데이터의 표현을 의미 공간의 점으로 나타내며, 이는 유클리드 기하학의 2D 공간과 유사하지만 고차원이다. 즉, 유클리드 기하학에서 평면의 점들이 좌표에 따라 가깝거나 멀 수 있는 것처럼, 의미 공간에서도 점의 근접성이 의미의 유사성을 반영한다. 비슷한 주제에 대한 문장들은 이 다차원 공간에서 서로 가까이 위치하게 되며, 이는 그래프에서 가까운 점들이 위치하는 것과 유사하다. 이러한 근접성은 텍스트 분류, 의미 검색, 제품 추천과 같은 작업에 도움을 주며, AI가 관련 개념을 "위치"에 따라 식별하고 그룹화할 수 있게 한다.
이 의미 공간을 벡터로 생각할 수 있다.
RAG란 주어진 질문이나 요청에 대해 보다 정확하고 관련성 높은 응답을 생성하기 위해 외부 데이터베이스나 문서에서 정보를 검색하고 이를 바탕으로 새로운 텍스트를 생성하는 방식이다.
Generation이란 주어진 입력을 기반으로 텍슽, 이미지, 코드등의 콘텐츠를 생성하는 프로세스를 의미한다. 이는 주로 자연어 처리(NLP) 모델이나 생성 모델을 활용하여 수행되며, 사용자가 제공한 프롬프트나 조건에 따라 다양한 형태의 출력을 생성할 수 있다.
Tokens
토큰(Token)은 AI 모델의 작동 방식의 기본 구성 요소이다. 입력 시 모델은 단어를 토큰으로 변환하고, 출력 시에는 토큰을 다시 단어로 변환한다.
영어에서 하나의 토큰은 대략 75%의 단어에 해당한다. 예를 들어, 셰익스피어의 전체 작품은 약 90만 단어로, 이는 대략 120만 개의 토큰으로 변환된다.
토큰은 비용과 직결되기 때문에 매우 중요하다. 호스팅된 AI 모델의 경우, 사용한 토큰 수에 따라 요금이 결정된다. 입력과 출력 모두 전체 토큰 수에 기여한다.
모델은 단일 API 호출에서 처리할 수 있는 텍스트 양에 제한이 있다. 이 한계를 "컨텍스트 윈도우(context window)"라고 하며, 이 제한을 초과하는 텍스트는 처리되지 않는다.
예를 들어, ChatGPT3는 4K 토큰 제한이 있으며, GPT4는 8K, 16K, 32K와 같은 다양한 옵션을 제공한다. Anthropic의 Claude AI 모델은 100K 토큰 제한을 가지며, 메타의 최근 연구에서는 1M 토큰 제한 모델이 개발되었다.
셰익스피어의 작품을 GPT4로 요약하려면, 데이터 조각을 잘라서 모델의 컨텍스트 윈도우 제한 내에서 데이터를 제시하는 소프트웨어 엔지니어링 전략을 마련해야 한다. Spring AI 프로젝트는 이 작업을 돕는다.
Structured Output Converter
AI 모델의 출력은 전통적으로 java.lang.String 형태로 제공된다. 사용자가 JSON 형식으로 응답을 요청하더라도, 결과는 올바른 JSON일 수 있지만 실제 JSON 데이터 구조가 아니다. 단순한 문자열일 뿐이다. 따라서 프롬프트에 "JSON" 요청을 포함하는 것이 항상 100% 정확하지는 않다.
이러한 복잡성으로 인해, 의도한 출력을 생성하기 위한 프롬프트를 작성하고, 결과적으로 생성된 단순 문자열을 애플리케이션 통합을 위한 사용 가능한 데이터 구조로 변환하는 전문 분야가 생겨났다.
구조화된 출력 변환(Structured output conversion)은 세심하게 설계된 프롬프트를 사용하며, 원하는 형식을 얻기 위해 모델과의 여러 번의 상호작용이 필요할 수 있다. 이 과정은 출력이 애플리케이션에 통합될 수 있도록 적절하게 포맷팅되도록 돕는다.
AI 모델의 정확도 향상시키기
GPT와 같은 AI 모델은 특정 시점까지만 공부된 특성을 가진다. 예를 들어 GPT 3.5/4의 경우는 2021년 9월까지의 데이터만 학습한 상태여서 그 이후의 데이터는 잘 모르고 적절하게 자기가 가진 데이터를 조합해서 할루시네이션을 유발하기도 한다. 이를 극복하기 위해 3가지 기술이 사용된다.
Fine Tuning
이 방법은 기존의 기계 학습 기법으로 모델의 내부 가중치를 조정하여 특정 데이터에 맞게 모델을 조정하는 방법이다. 하지만 이 과정은 상당히 복잡하고 자원이 많이 소모된다. 이미 잘 배워온 GPT 다시 훈련시킨다고 생각하면 필요한 리소스가 얼마나 많을까? 생각만 해도 아득하다. 또한 폐쇄형의 모델에서는 불가능한 일일 수도 있다.
Prompt Stuffing(프롬프트 채우기)
Find Tuning 보다 실용적인 대안은 사용자의 데이터를 모델에 제공하기 위해 프롬프트 내에 데이터를 삽입하는 방법이다.
Prompt Stuffing의 가장 대표적인 방법은 RAG(Retrieval Argumented Generation: 검색 증강 생성)으로 Spring AI 라이브러리를 이용하면 손쉽게 이를 처리할 수 있다.
RAG는 AI 모델이 보다 정확하고 유용한 응답을 생성하기 위해 외부 데이터를 통합하는 빙식으로 이 방법은 정보 검색과 텍스트 생성을 결합하여 사용자 질문에 대한 최적의 답변을 제공하는데 도움을 준다.
RAG는 크게 정보 검색과 텍스트 생성이라는 두 가지로 구성된다. 먼저 정보 검색 분야는 사용자의 질문에 관련된 정보를 포함하는 문서나 조각을 검색하는데 이 과정에서 백터 데이터베이스를 활용한다. 텍스트 생성 분야에서는 검색된 정보를 바탕으로 AI 모델이 새로운 텍스트를 생성하는 과정이다. 이 단계에서는 자연어 처리 기술이 활용된다.
RAG 크게 두 단계를 거쳐 동작하게 된다.
첫 번째 단계 비정형 데이터를 준비하는 단계이다. 원본 문서를 작은 조각으로 나누어 벡터 데이터베이스에 저장한다. 이를 ETL(Extract-추출, Transform-변환, Load-적재)라고 한다. 이때 의미적 경계를 유지하는 것이 중요하다. 단락과 그에 대한 표가 있는데 둘을 분리해버리면 안된다. 또한 작은 단위로 나눠서 AI 모델의 토큰 제한에 맞추는 것도 중요하다.
다음 단계는 사용자의 입력을 처리하는 단계이다. 사용자가 질문을 입력하면 해당 질문과 유사한 문서 조각들이 함께 AI 모델에 전달된다. 이를 통해 모델은 더 풍부한 정보를 바탕으로 응답을 생성할 수 있다. 이런 유사한 조각을 찾는데 벡터 데이터베이스가 아주 효율적이다.
Function Call(함수 호출)
LLM은 기본적으로 학습했을 때까지 지식으로 동작하기 때문에 추가로 외부 데이터에 접근하거나 수정할 수 없는데 Function Call 메커니즘은 이런 단점을 개선할 수 있다. Function Call을 이용하면 LLM을 외부 시스템의 API와 연결할 수 있게 되는데 외부 시스템은 LLM에 실시간 데이터를 제공하고 대신 데이터 처리 작업을 수행할 수도 있다.
Spring AI에서는 이 함수를 @Bean 형태로 제공한 다음 프롬프트 옵션에서 함수의 빈 이름을 제공하면 해당 함수를 활서와 시킬 수 있다.
다음은 Function Call의 동작 절차이다.
- 함수 정의 정보(function definition)를 보내는 채팅 요청을 수행한다. function definition은 이름, 설명, 입력 매개변수로 구성된다.
- 모델은 name에 해당하는 함수를 호출할 때 입력 매개변수를 전달하고 결과는 다시 모델에 전달된다.
- 이 과정은 Spring AI가 대신 처리해준다.Spring AI는 이름에 해당하는 함수를 호출하면서 전달받은 입력 매개변수를 넘겨주고 그 결과를 모델에 반환한다.
- 모델은 필요한 모든 정보를 검색하기 위해 여러 번의 함수 호출을 수행할 수 있다.
- 필요한 모든 정보를 획득하면 모델은 응답을 생성한다.
소중한 공감 감사합니다