| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- UI/UX
- 자바 포맷 출력
- ListView
- develop
- JQ
- 앱심사
- Clean Architecture
- riverpod
- DART
- java 콘솔 출력 차이
- java 출력
- npm
- firebase
- react
- nodejs
- 자바스크립트
- Flutter
- println
- scss
- lifecycle
- unity
- 자바 출력 방식
- abap
- LLM
- java
- JS
- 단축키
- printf
- 엡
- 배포
- Today
- Total
guricode
[자취의 정석] 자취도우미 ai 챗봇 만들기 - api연결, data레이어 작성 본문

자취에 대한 질의응답과 냉장고 레시피를 추천해주는 ai 챗봇을 만들려고한다
ai 서비스는 여러가지가 있다
open ai, 구글 재미나이, 클로드 등등
만들려는 채팅 서비스의 복잡도를 생각해봤을때 그렇게 많은 요구가 필요하지 않다.
기능의 범위는 자취팁, 레시피 추천 정도니까 간단하다고 생각한다.
구글 재미나이에 무료 할당량이 많기 때문에 재미나이 api를 이용해서 ai채팅 서비스를 만들려고한다.
대화 히스토리는 firebase에 저장된다.
일단 프로젝트에 적용하기 위해 의존성 추가를 한다.
google_generative_ai: ^0.4.7
flutter pub get을 해준다
이제 재미나이 API 키를 발급받아야한다.
Gemini API 키 발급 방법:
Google AI Studio 접속: https://ai.google.dev/
Gemini Developer API | Gemma open models | Google AI for Developers
Build with Gemini 2.0 Flash, 2.5 Pro, and Gemma using the Gemini API and Google AI Studio.
ai.google.dev
Google 계정으로 로그인한 후 ai스튜디오로 이동한다

왼쪽 메뉴아래쪽에보면 "Get API Key" 클릭
" API 키 만들기" 선택

그러면 키 이름과 프로젝트 선택란이 나오는데 적용할 프로젝트를 선택 후 키 만들기를 누르면 된다.

키가 만들어 지면 암호화를 위해 API 키 복사 후 setting.env 파일에 붙여넣는다.
.env파일은 깃 이그노어해놓자
그리고 api가 잘 들어오는지 테스트 코드를 작성해야한다.
이제 클린 아키텍쳐의 데이터 소스를 만들어야한다.(으악)
반환되는 타입은 메세지이며 String으로 받는데
레시피 추천과 일반 채팅으로 나눠야한다.
abstract interface class GeminiDataSource {
//일반 메세지전송
Future<String> sendMessage(String message, List<Map<String, String>> history);
//냉장고 재료 기반으로 레시피 생성
Future<String> generateRecipe(
List<String> ingredients,
List<Map<String, String>> history,
);
//인삿말
Future<String> generateGreeting();
추상클래스로 필요한 메서드들을 선언해준다.
데이터소스를 만들었으니 데이터소스를 구현하는 구현체를 만들어준다
class GeminiDataSourceImpl implements GeminiDataSource {
final GenerativeModel _model;
// 생성자에서 Gemini 모델을 초기화
GeminiDataSourceImpl(String apiKey)
: _model = GenerativeModel(
model: 'gemini-2.0-flash',
apiKey: apiKey,
systemInstruction: Content.text('''-모델 전역 프롬프트 내용'''),
generationConfig: GenerationConfig(
temperature: 0.3,
topP: 0.9,
),
safetySettings: [
SafetySetting(
HarmCategory.harassment,
HarmBlockThreshold.medium,
),
SafetySetting(
HarmCategory.hateSpeech,
HarmBlockThreshold.medium,
),
SafetySetting(
HarmCategory.sexuallyExplicit,
HarmBlockThreshold.medium,
),
SafetySetting(
HarmCategory.dangerousContent,
HarmBlockThreshold.medium,
),
],
);
GenerativeModel은 재미나이 ai모델을 사용할수 있게 해주는 클래스다.
_model로 초기화해준다.
flash모델이 비용이 저렴해서 채팅에 적합하다고한다.
pro모델은...비용이 비싸다..
generationConfig은 생성제어 파라미터다.
temperature는 창의성을 나타낸다. 0.0~1.0까지 수치가 있으며 낮을수록 일관된 답변을 해준다.
topP는 수치가 높을수록 창의적인 답변 후보가 나온다. 0.9면 조금 자유로운 정도?
safetySettings은 욕설,괴롭힘,성적,위험행위 차단해주는 셋팅이다. 권장 레벨인 medium을 사용했다.
senfMessage구현체
@override
Future<String> sendMessage(
String message,
List<Map<String, String>> history,
) async {
try {
// 대화 히스토리를 Content 객체로 변환
final List<Content> contents = [];
// 이전 대화 히스토리 추가
for (final msg in history) {
if (msg['role'] == 'user') {
contents.add(Content.text(msg['content']!));
} else if (msg['role'] == 'assistant') {
contents.add(Content.model([TextPart(msg['content']!)]));
}
}
// 현재 메시지 추가
contents.add(Content.text(message));
// Gemini API 호출
final response = await _model.generateContent(contents);
return response.text ?? '죄송합니다. 응답을 생성할 수 없습니다.';
} catch (e) {
throw Exception('메시지 전송 실패: $e');
}
}
message는 현재 유저가 작성한 메세지.
history는 이전 대화를 참고할수 있게 List로 만든 파라미터다.
여기서 Content타입은 api가 이해할수 있는 메세지 형태의 클래스다.
Content.text는 사용자메세지, Content.model은 ai응답이다.
나는 오로지 텍스트만 주고받기로 했기때문에 map형태의 String으로 만들었따.
여기서 history의 role은 비즈니스로직이므로 레파지토리에서 따로 구현한다.
generateContent를 이용해 모델에 전송하면 response로 응답을 받는다.
이 응답은 response.text로 꺼내올수있다.
래시피 추천 구현체
@override
Future<String> generateRecipe(
List<String> ingredients,
List<Map<String, String>> history,
) async {
try {
// 재료 기반 레시피 생성 프롬프트
final prompt =
'''
사용자가 가진 재료: ${ingredients.join(', ')}
이 재료들로 만들 수 있는 자취생 맞춤 레시피를 추천해주세요.
조건:
- 30분 이내 조리 가능
- 추가 재료 최소화 (기본 조미료만 사용)
- 난이도: 초보자도 가능
- 1-2인분 기준
- 경제적이고 영양가 있는 요리
레시피 형식:
🍳 [요리명]
⏰ 조리시간: XX분
🥘 재료: [필요한 재료]
👨🍳 조리법:
1. ...
2. ...
3. ...
💡 꿀팁: [추가 조언]
''';
// 대화 히스토리와 함께 전송
final response = await sendMessage(prompt, history);
return response;
} catch (e) {
throw Exception('레시피 생성 실패: $e');
}
}
문자열에서 요리 재료 추출에 좀더 도움되도록 presentation레벨에서 문자열을 파싱해 리스트로 만들 예정이다.
그런데 문자열에 재료가 아닌 것들이 들어가있을때 제대로 답변이 나올지 의심스러웠다.
그래서 유닛테스트를 진행해서 결과를 확인했다.

결과는 만족스러웠다.
'앱 > Flutter&Dart' 카테고리의 다른 글
| [자취의 정석] 자취도우미 ai 챗봇 만들기, XOR, Object 키워드, Entity -클린 아키텍쳐 Domain 레이어 (0) | 2025.10.14 |
|---|---|
| [Dart] 자주쓰는 Object 키워드 (예약어) (0) | 2025.10.14 |
| [자취의 정석] Flutter 앱에 "전체" 카테고리 탭 추가하기: Clean Architecture 기반 구현 (0) | 2025.10.12 |
| [트러블슈팅] 댓글 작성 후 리스트 반영이 안될 때 (0) | 2025.10.12 |
| [Flutter] GoRouter 라우터 정리 – push, go, pushReplacement 차이 (0) | 2025.10.10 |