Loading the catalog…
Loading the catalog…
큰 목표 LangChain(데이터 검색 및 LLM 연동), FastAPI(백엔드 API 구축), Flutter(모바일 앱 UI)를 결합하여 나만의 RAG(검색 증강 생성) 챗봇 [Back] 1. 파이썬 백엔드 환경 및 패키지 설치 pip install fastapi uvicorn langchain langchain-openai langchain-community faiss-cpu python-dotenv # or uv add fastapi uvicorn langchain langchain-openai langchain-community faiss-cpu python-dotenv OPENAI_API_KEY=sk-본인의_api_키를_입력하세요 2. FastAPI 및 RAG 파이프라인 구축 임의의 텍스트 데이터를 벡터 DB(FAISS)에 임베딩하여 RAG 파이프라인을 구축하고, 이를 API로 노출하는 코드 외부 라이브러리 임포트 import os from fastapi import FastAPI from pydantic import BaseModel from langchain_community.vectorstores import FAISS from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain.schema import Document from langchain.chains import create_retrieval_chain from langchain.chains.combine_documents import create_stuff_documents_chain from langchain_core.prompts import PromptTemplate from dotenv import load_dotenv load_dotenv() app = FastAPI() 실습용 데이터 임베딩 및 벡터 저장소 생성 sample_texts = [ "플러터(Flutter)는 구글이 개발한 오픈소스 UI 크로스 플랫폼 프레임워크입니다.", "LangChain은 LLM 기반 애플리케이션 개발을 돕기 위해 만들어진 프레임워크입니다.", "FastAPI는 파이썬 3.6 이상을 위한 빠르고 직관적인 서버 프레임워크입니다.", "RAG는 데이터베이스에서 관련 정보를 검색한 후 LLM의 답변 정확도를 높이는 기술입니다." ] docs = [ Document(page_content=text) for text in sample_texts ] # print(docs) # [Document(metadata={}, page_content='플러터(Flutter)는 구글이 개발한 오픈소스 UI 크로스 플랫폼 프레임워크입니다.'), Document(metadata={}, page_content='LangChain은 LLM 기반 애플리케이션 개발을 돕기 위해 만들어진 프레임워크입니다.'), Document(metadata={}, page_content='FastAPI는 파이썬 3.6 이상을 위한 빠르고 직관적인 서버 프레임워크입니다.'), Document(metadata={}, page_content='RAG는 데이터베이스에서 관련 정보를 검색한 후 LLM의 답변 정확도를 높이는 기술입니다.')] # OpenAI 임베딩 및 FAISS 벡터 DB 구축 # 벡터 데이터베이스 및 검색기(Retriever) 설정 # FAISS.from_documents: # Document 객체 리스트(docs)를 OpenAIEmbeddings() 모델을 사용해 벡터(숫자 배열)로 변환한 뒤, # FAISS라는 빠르고 가벼운 인메모리 벡터 DB에 저장 vectorstore = FAISS.from_documents(docs, OpenAIEmbeddings()) # as_retriever: # 구축된 백터 DB를 LangChain 체인에서 사용할 수 있는 '검색기' 객체로 변환 # 사용자가 질문을 던지면, 이 객체가 질문과 가장 유사한 문서를 DB에서 찾아옴 retriever = vectorstore.as_retriever() print(retriever) # 검색기 설정 상태 출력 (검색 알고리즘, 반환할 문서 개수 K 등의 기본 설정 확인 가능) 최신 LangChain 방식(LCEL)의 RAG 체인 구성 # ChatOpenAI: # 답변을 생성할 대형 언어 모델(LLM)을 정의 # temperature=0으로 설정하면 모델이 창의적인 답변보다는 일관되고 사실적인(결정론적) 답변을 생성하도록 제한 llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) # PromptTemplate.from_template: # 하나의 긴 텍스트 문자열(String) 형태로 프롬프트를 구성 # {context}에는 retriever가 찾아온 문서 내용이 들어가고, {input}에는 사요자의 질문이 들어감 prompt = PromptTemplate.from_template( "다음 문맥(context)을 바탕으로 질문(Question)에 답하세요.\n\n문맥: {context}\n\n 질문: {input}]n답변:" ) # create_stuff_documents_chain: # 찾아온 여러 개의 문서(Documents)를 프롬프트이 {context} 변수에 "그대로 쑤셔 넣는(stuff)" 체인임 # 문서 내용과 프롬프트를 합쳐서 LLM에게 전달하는 역할을 함 combine_docs_chain = create_stuff_documents_chain(llm, prompt) # create_retrieval_chain: # 검색(retriever)과 문서 결합(combine_docs_chain)을 하나의 파이프라인으로 연결 # 체인을 실행(invoke)하면 -> 사용자의 질문 수신 -> retirieval가 관련 문서 검색 # -> combine_docs_chain이 프롬프트 완성 -> LLM이 최종 답변 생성의 순서로 자동 실행 retrieval_chain = create_retrieval_chain(retriever, combine_docs_chain) FastAPI 엔드포인트 라우터 정의 # 데이터 검증 모델 정의 (Pydantic) # BaseModel을 상속받아 클라이언트(플러터 앱)와 서버(FastAPI)가 주고받을 데이터의 '형태(스키마)'를 정의 # 이 클래스들은 데이터가 올바른 타입(str, int 등)인지 자동으로 검사(Validation) 해 줌 class QueryRequest(BaseModel): # 클라이언트가 서버로 보낼 때 사용할 형식 # JSON 형태의 {"query": "플러터가 뭐야?"} 데이터가 들어올 것을 예상하고, # 'query'라는 키값과 문자열(str) 타입의 데이터를 필수로 요구함 query: str class QueryResponse(BaseModel): # 서버가 작업을 마치고 클라이언트에게 돌려줄 응답의 형식 # JSON 형태의 {"answer": "플러터는 구글이 개발한..."} 형태로 반환하겠다고 약속 answer: str @app.post("/ask", response_model=QueryResponse) async def ask_question(request: QueryRequest): # async def: 비동기 함수로 선언 # LLM이 답변을 생성하는 데 몇 초가 걸리더라도, 서버가 멈추지 않고 다른 사용자의 요청을 동시에 받을 수 있게 해줌 # retrieval_chain.invoke: 준비해둔 RAG 파이프라인을 가동 # 앞서 정의한 PromptTemplate에서 {input}이라는 변수를 사용했기 때문에, # 딕셔너리 형태로 {"input": request.query (사용자 질문)}을 짝지어 넘겨줌 result = retrieval_chain.invoke({"input": request.query}) # 체인이 성공적으로 완료되면 result 변수에는 여러 정보가 딕셔너리 형태로 담긴다 # 예: {"input": "...", "context": [검색된 문서들], "answer": "최종 생성된 답변"} # 여기서는 플러터 앱이 필요로 하는 최종 답변(result["answer"])만 추출 # QueryResponse 형식에 맞춰 객체를 생성하여 반환하면, # FastAPI가 이를 자동으로 JSON 포맷({"answer": "..."})으로 변환하여 플러터 앱에 전송 return QueryResponse(answer=result["answer"]) 결과 확인 ChatPromptTemplate 대신 PromptTemplate을 사용한 이유 작동의 단순함을 위해 PromptTemplate이 사용되었다. 하지만 사용중인 모델(ChatOpenAI)의 특성을 고려하면 ChatPromptTemplate을 사용하는 것이 더 권장되는 방식이다. 두 템플릿의 차이와 작동 원리 PromptTemplate 역할을 구분하지 않고 단일 텍스트 문자열을 만든다. gpt-3.5-turbo와 같은 책 모델은 기본적으로 [System, User, Assistant] 구조의 메시지를 받도록 설계되어 있음. LangChain은 편의를 위해 PromptTemplate으로 생성된 단일 문자열을 내부적으로 강제 변환하여 User(Human) 메시지로 LLM에 전달한다. 작동은 하지만, 챗 모델의 의도된 사용법을 100% 활용하는 것은 아님 ChatPromptTemplate 시스템 프롬프트(AI의 역할과 참조할 문맥)와 휴먼 프롬프트(사용자의 실제 질문)를 명확히 분리 AI가 지시사항을 더 정확하고 따르고 환각(Hallucination) 현상을 줄이는 데 유리 ChatPromptTemplate으로 변경할 경우의 코드 예시 from langchain_core.prompts import ChatPormptTemplate # System 메시지와 Human 메시지를 분리하여 더 명확하게 지시 prompt = ChatPrompttemplate.from_messages([ ("system", "당신은 주어진 문맥(context)만을 바탕으로 질문을 답하는 유능한 어시스턴트입니다.\n\n문맥: {context}), ("human", "{input}") ]) 결론적으로 단순한 실습이나 테스트에서는 PromptTemplate을 써도 정상적으로 동작하지만, 실제 프로덕션 수준의 RAG를 구축할 때는 AI에게 명확한 페르소나와 지시를 내리기 위해 ChatPromptTemplate을 사용하는 것이 좋음 [Flutter] import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'RAG 실습', theme: ThemeData(primarySwatch: Colors.blue), home: const ChatScreen(), ); } } class ChatScreen extends StatefulWidget { const ChatScreen({super.key}); @override State<ChatScreen> createState() => _ChatScreenState(); } class _ChatScreenState extends State<ChatScreen> { final TextEditingController _controller = TextEditingController(); String _answer = "궁금한 기술(Flutter, LangChain 등)을 질문해보세요."; bool _isLoading = false; Future<void> _askQuestion() async { final query = _controller.text; if (query.isEmpty) return; setState(() { _isLoading = true; _answer = "답변을 생성 중입니다..."; }); try { // Android 에뮬레이터는 10.0.2.2, iOS 시뮬레이터/웹은 localhost(127.0.0.1)를 사용합니다. final url = Uri.parse('http://localhost:8000/ask'); final response = await http.post( url, headers: {'Content-Type': 'application/json'}, body: jsonEncode({'query': query}), ); if (response.statusCode == 200) { // 한글 깨짐 방지를 위해 utf8.decode 사용 final data = jsonDecode(utf8.decode(response.bodyBytes)); setState(() => _answer = data['answer']); } else { setState(() => _answer = "오류 발생: \${response.statusCode}"); } } catch (e) { setState(() => _answer = "서버 통신 실패: \$e"); } finally { setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('RAG 챗봇 (FastAPI + LangChain)')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Expanded( child: SingleChildScrollView( child: Text(_answer, style: const TextStyle(fontSize: 16)), ), ), Row( children: [ Expanded( child: TextField( controller: _controller, decoration: const InputDecoration( hintText: '질문을 입력하세요...', border: OutlineInputBorder(), ), ), ), const SizedBox(width: 8), ElevatedButton( onPressed: _isLoading ? null : _askQuestion, child: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('전송'), ), ], ), ], ), ), ); } } 결과 정리 학습한 핵심 기술 개념 요약 LangChain을 활용한 프롬프트 엔지니어링(AI) PromptTemplate & ChatPromptTemplate: LLM에게 단순히 사용자의 질문만 던지는 것이 아니라, ‘너는 친절한 AI 어시스턴트야’와 같은 페르소나(System Message)를 부여하고, 사용자의 입력(Human Message)을 변수로 받아 구조화된 프롬프트를 조립하는 방법을 익힘 체인(Chain) 구성: 프롬프트 템플릿과 LLM 모델을 연결하여, 데이터가 입력되고 결과가 출력되는 일련의 파이프라인 흐름을 이해함 FastAPI와 Pydantic을 이용한 견고한 백엔드 구축 (Backend) Pydantic 데이터 검증: 프론트엔드에서 날아오는 JSON 데이터가 정확한 규격(타입, 필수 값 등)을 갖추었는지 사전에 검증(Validation) 하여 서버 에러를 방지하는 모델링을 적용 비동기 API 라우팅: LLM의 응답을 기다리는 동안 서버가 멈추지 않도록 비동기(async def)로 API 엔드포인트를 설계하여 프론트엔드와 통신할 수 있는 브리지를 마련 Flutter와 REST API 연동 (Frontend) HTTP 비동기 통신: http 패키지를 사용하여 프론트엔드에서 백엔드로 POST 요청을 보내고, 비동기(astnc/await)로 서버의 응답을 기다렸다가 받아오는 통신 사이클을 구현 상태 관리와 UI 렌더링: 백엔드에서 반환된 LLM의 텍스트 답변 데이터를 Flutter의 상태(State)로 업데이트하여, 사용자의 화면에서 실시간으로 렌더링하는 데이터 흐름을 완성 전체 시스템 아키텍처(Architecture) Client - Server - LLM 구조: 프론트엔드(Flutter)가 직접 LLM API 키를 들고 통신하는 보안상 위험한 구조가 아니라, 백엔드(FastAPI)가 중간에서 요청을 받아 LLM(LangChain)과 통신하고 그 결과만 프론트엔드로 안전하게 전달해 주는 안전한 3-Tier 아키텍처의 뼈대를 세웠음 1단계의 한계점과 다음 스텝 통신 파이프라인은 성공적으로 연결했지만, 아직 ‘나만의 RAG 챗봇’이라고 부르기에는 몇 가지 한계가 존재함 단순 API 호출의 한계 현재 상태는 RAG(검색 증강 생성)라기보다는, 단순한 LLM API 호출에 가까움 외부 지식(PDF, 텍스트 문서 등)을 참고해서 답변하는 기능이 아직 없음 대화 맥락 소실 이전 대화 기록을 기억하지 못해 연속적인 질문과 답변(Context)이 불가능한 단발성 챗봇 상태 UI/UX 아쉬움 답변이 올 때까지의 로딩 처리(스트리밍 등)나 채팅 UI의 디테일이 부족함
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
LLM, LangChain, Rag, FastAPI Flutter 연습 - 1단계(기초 파이프라인부터). 큰 목표 LangChain(데이터 검색 및 LLM 연동), FastAPI(백엔드 API 구축), Flutter(모바일 앱 UI)를 결합하여 나만의 RAG(검색 증강 생성) 챗봇 [Back] 1. 파이썬 백엔드 환경 및 패키지 설치 pip install fastapi uvicorn langchain langchain-openai langchain-community faiss-cpu python-dotenv # or uv add fastapi uvicorn langchain langchain-openai langchain-community faiss-cpu python-dotenv…
Open source