데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      Flutter, Fast API 에서 LLM API 를 활용하기-3 (Bard-API vs OpenAI-API vs kakaoTrans)

      마법거북이 23.07.05
      2,662 6 0

      1편, 2편에 이어 말씀드릴 내용입니다.

      3편 : Flutter 프론트엔드 구현 및 결과 보기

      • 메뉴리스트 만들기 (app bar 및 drawer 만들기 )

      • gptApiScreen 만들기

      • GptController 만들기

      • 총평

      샘플 코드에서 Flutter 는 간편하게 구성해 보았습니다.

      입력과 출력을 한 화면에 구성하고 controller를 통해 Fast-API 를 호출합니다.

      Fast-API가 처리하는 동안 화면에서는 Indicator가 동작합니다.

      처리가 완료되면 결과를 화면에 출력합니다.

      샘플 코드라서 간편하게 구성을 하였지만, 실제 개발시에는 가장 많은 고민과 시간을 투입이 필요한 곳 입니다.


      메뉴리스트 만들기

      • git에서 받은 버전은 예전에 EmailScreen 화면만 구현을 해 두었기에 이번에 새로 만들 화면 GptApiScreen 간의 이동이 되어야 합니다.

        그래서 AppBar를 만들고 Drawer를 활용하여 메뉴 리스트를 만들어 보도록 하겠습니다.

      • 메뉴 선택시 화면 전환을 위해 routeName 을 static 으로 생성해 줍니다. 그리고 앱바를 Scaffold 에서 추가를 합니다.

      // lib > screens > email_screen.dart 파일 수정
      
      import 'package:devcoean_flutter/controller/email_controller.dart';
      ...
      
      class EmailScreen extends StatefulWidget {
        // 메뉴를 이동하기 위해서 routeName 변수 설정 정의 
        static const routeName = '/email-screen';
      ...
      }
      
      class _EmailScreenState extends State<EmailScreen> {
      
        
        Widget build(BuildContext context) {
          return Scaffold(
      	 // 앱바를 추가한다
            appBar: AppBar(
              title: Text("Devocean"),
            ),
            body: SingleChildScrollView(
             ... 
            ),
          );
        }
      }
      • Drawer를 만듭니다.

      // lib > screens > email_screen.dart 파일 수정
      
      import 'package:devcoean_flutter/controller/email_controller.dart';
      ...
      class _EmailScreenState extends State<EmailScreen> {
      
        
        Widget build(BuildContext context) {
          return Scaffold(
            appBar: AppBar(
              title: Text("Devocean"),
            ),
      		// 아래 Drawer 메뉴 추가 
             drawer: Drawer(
              child: ListView(
                padding: EdgeInsets.zero,
                children: [
                  DrawerHeader(
                    decoration: BoxDecoration(
                      color: Colors.blue,
                    ),
                    child: Text(
                      '화면리스트',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 24,
                      ),
                    ),
                  ),
                  ListTile(
                    title: Text('Email전송메뉴'),
                    onTap: () {
                      Navigator.of(context).pushReplacementNamed("/email-screen");
                    },
                  ),
                  ListTile(
                    title: Text('Gpt테스트메뉴'),
                    onTap: () {
                      Navigator.of(context).pushReplacementNamed("/gpt-screen");
                    },
                  ),
                ],
              ),
            ),
            body: SingleChildScrollView(
             ... 
            ),
          );
        }
      }
      • Appbar 와 Drawer는 대부분의 화면에 공통으로 사용되기 때문에 Appbar Widget으로 만드는게 좋습니다. 지금은 우선 pass 하겠습니다.


      gptApiScreen 만들기

      • 화면의 레이아웃 입니다. 입력을 받고 버튼을 누르면 Indicator가 동작합니다.

      • api에서 response를 받으면 Indicator를 종료하고 화면에 결과를 출력하는 코드 입니다.

      
      import 'package:devcoean_flutter/constants/constants.dart';
      import 'package:devcoean_flutter/controller/gpt_controller.dart';
      import 'package:flutter/material.dart';
      import 'package:devcoean_flutter/widgets/dev_textfield_widget.dart';
      
      // 전송을 누르면 로딩이 되었다가, api응답이 오면 다시 화면을 렌더링 해야하기 때문에
      // Stateful 위젯으로 생성합니다. 
      // email_screen.dart 파일과 매우 유사 합니다. 
      class GptApiScreen extends StatefulWidget {
      
      	// 메뉴를 이동하기 위해서 routeName 변수 설정 정의 
        static const routeName = '/gpt-screen';
        const GptApiScreen({Key? key}) : super(key: key);
      
        
        State<GptApiScreen> createState() => _GptApiScreenState();
      }
      
      class _GptApiScreenState extends State<GptApiScreen> {
        final TextEditingController _titleTextCtrl = TextEditingController();
        String? bardResult; // bard API 결과를 저장할 변수
        String? openAIResult;  // openAI API 결과를 저장할 변수
        String? kakaoResult; // kakao API 결과를 저장할 변수 
        var myFormState; // 상태를 관리할 enum 변수 Ready, Running, Done
      
      	// 초기화 
        
        void initState() {
          bardResult = "";
          openAIResult = "";
          kakaoResult = "";
          myFormState = processState.Ready;
          super.initState();
        }
      
        
        Widget build(BuildContext context) {
          return Scaffold(
      			// 앱바와 Drawer는 코드는 동일
            appBar: AppBar(
              title: Text("Devocean"),
            ),
            drawer: Drawer(
              child: ListView(
                padding: EdgeInsets.zero,
                children: [
                  DrawerHeader(
                    decoration: BoxDecoration(
                      color: Colors.blue,
                    ),
                    child: Text(
                      '화면리스트',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 24,
                      ),
                    ),
                  ),
                  ListTile(
                    title: Text('Email전송메뉴'),
                    onTap: () {
                      Navigator.of(context).pushReplacementNamed("/email-screen");
                    },
                  ),
                  ListTile(
                    title: Text('Gpt테스트메뉴'),
                    onTap: () {
                      Navigator.of(context).pushReplacementNamed("/gpt-screen");
                    },
                  ),
                ],
              ),
            ),
            // 자식 위젯들이 화면에서 넘어가는 경우 Scroll 이 될 수 있도록 SingleChildScrollView 적용  
            body: SingleChildScrollView(
      		    // 전체 출력은 화면의 90%만 사용
              child: Container(
                height: MediaQuery.of(context).size.height * 0.9,
                padding: const EdgeInsets.all(8),
                // 컬럼 위젯은 중앙에서 시작하고 가로는 순서대로 꽉차게 (stretch) 
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    // 텍스트 필드 위젯     
                    DevTextFieldWidget(
                      TextCtrl: _titleTextCtrl,
                      hint: "title",
                    ),
                    // 간격 
                    const SizedBox(height: 20),
                    // API를 호출할 버튼 , 버튼을 누르면 Running 상태로 상태를 변화 시키고 
                    // GptController의 gptTranslate 함수 호출
                    // 결과가 완료되면 Done 상태로 만들고, 각각의 변수에 결과를 할당  
                    ElevatedButton(
                      onPressed: () async {
                        if (mounted) {
                          setState(() {
                            myFormState = processState.IsRunning;
                          });
                          await GptController()
                              .gptTranslate(_titleTextCtrl.text)
                              .then((result) {
                            setState(() {
                              myFormState = processState.Done;
                              bardResult = result["bard_msg_ctt"];
                              openAIResult = result["openai_msg_ctt"];
                              kakaoResult = result["kakao_msg_ctt"];
                            });
                          });
                        }
                      },
                      child: const Text("GPT 테스트"),
                    ),
                    const SizedBox(height: 50),
                    // Running 상태인경우, 버튼을 누른경우 에는
                    // 버튼의 하단 중간 영역에서 처리 중임을 표시 
                    if (myFormState == processState.IsRunning)
                      const Center(
                        child: CircularProgressIndicator(),
                      )
                    // API에서 결과를 받았다면 결과를 화면에 출력 
                    else if (myFormState == processState.Done)
                      Container(
                        height: MediaQuery.of(context).size.height * 0.5,
                        child: ListView.builder(
                            shrinkWrap: true,
                            itemCount: 3,
                            itemBuilder: (BuildContext content, int index) {
                              if (index == 0) {
                                return ListTile(
                                  leading: Text("bard result : "),
                                  title: Text(bardResult!),
                                );
                              }
                              if (index == 1) {
                                return ListTile(
                                  leading: Text("openai result :"),
                                  title: Text(openAIResult!),
                                );
                              }
                              if (index == 2) {
                                return ListTile(
                                  leading: Text("kakao result :"),
                                  title: Text(kakaoResult!),
                                );
                              }
                            }),
                      )
                    // 준비 상태 에서는 아무것도 보여주지 음
                    else
                      SizedBox(),
                  ],
                ),
              ),
            ),
          );
        }
      }


      GptController 만들기

      • Fast-API 서버에 request 하고 reponse를 받아오는 역할의 Controller

      • 싱글톤 형식으로 만든다

      import 'dart:convert';
      
      import 'package:devcoean_flutter/models/email_model.dart';
      import 'package:http/http.dart' as http;
      import '../constants/constants.dart';
      
      class GptController {
        // 싱글턴 형식으로 사용한다
        static final GptController _instance = GptController._internal();
      
        factory GptController() {
          return _instance;
        }
      
        GptController._internal() {}
      
        // titleNm 을 body에 넣어 request 하고 그 결과를 받아서 리턴한다 
        Future<dynamic> gptTranslate(String titleNm) async {
          try {
            var fastApiUrl;
            fastApiUrl = Uri.parse(baseUrl + '/gpt/translate/by/gpt');
            http.Response response = await http.post(
              fastApiUrl,
              body: jsonEncode(
                {"title_nm": titleNm},
              ),
              headers: {
                'accept': 'application/json',
                'Content-Type': 'application/json'
              },
            );
      
            final responseData = json.decode(utf8.decode(response.bodyBytes));
            print(responseData);
      
            if (response.statusCode != 200) {
              final errorMessage = responseData['detail'];
              print("error : $errorMessage");
              // throw Exception(errorMessage);
            }
      
            return responseData;
          } catch (error) {
            throw error;
          }
        }
      }
      • route를 위해서 main.dart 파일 수정

      import 'screens/email_screen.dart';
      import 'screens/gpt_api_screen.dart';
      import 'package:flutter/material.dart';
      
      void main() {
        runApp(const MyApp());
      }
      
      class MyApp extends StatelessWidget {
        const MyApp({Key? key}) : super(key: key);
      
        // This widget is the root of your application.
        
        Widget build(BuildContext context) {
          return MaterialApp(
            title: 'Flutter Demo',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            // GptApiScreen 을 홈으로 설저
            home: GptApiScreen(),
            // 여기에 routes 를 정의를 해줘야 routeName 으로 화면 전환이 가능합니다.      
            routes: {
              GptApiScreen.routeName: (context) => GptApiScreen(),
              EmailScreen.routeName: (context) => EmailScreen(),
            },
          );
        }
      }
      • 이제 애뮬레이터에 Flutter 를 실행을 시켜 봅니다.

      image.png

      image.png

      image.png

      image.png


      총평

      kakaotrans는 무료이면서도 매우 사용하기 쉬웠고 번역 결과 역시 만족스러웠습니다.

      하지만 LLM 이라기 보다는 NLP 라이브러리로 일반 번역을 해도 해외 상품을 이해하기 어려운 경우가 종종 있었습니다.

      openai 는 제품의 설명을 잘 이해하게 끔 동작했습니다.

      내용의 완성도도 만족스러웠습니다.

      다만 유료라는 점이 가장 불편했습니다.

      제공하는 모델들이 많고, 튜닝의 요소들이 많이 있습니다. 이에 따라서 어떻게 튜닝하는가에 따라서 결과가 조금 다르게 보여지기도 했습니다.

      그래서 사용자체는 매우 쉬우나, 커스텀마이징을 할려면 공부와 테스트는 필요합니다.

      모델 역시 과거 모델을 사용하기 때문에 최신의 트렌드는 따라가지 못할 우려가 있었습니다. 최신은 더 비쌈 ㅜ

      bardapi는 openai 의 API와 견주어 성능이나 내용의 완성도가 비슷합니다. 경우에 따라 더 나을 때도 있어 보입니다.

      최신의 내용도 학습되어 있기도 합니다. 더욱이 무료입니다! (언제까지 제공될지는 모르겠지만)

      그래서 bardapi 를 사용하고자 했는데.. 결국에는 사용하지 못했습니다.

      이유는 초당 여러건의 api를 호출하게 되면 거부가 됩니다. rate limiting이 걸려있는 것 같았습니다.

      초당으로 걸려 있다면, 분당, 일일 호출수도 얼마인지는 모르지만 limiting이 있을 것으로 추측을 합니다.

      결론을 내리면 단어 처리 문구 처리, 단순 번역 등을 위해서는 kakaoTrans 활용

      api를 초당 여러건 안전하게 처리해야 하는 서비스인 경우는 Open AI API

      간간히 1~2건 사용하는 경우는 Bard를 사용하는게 아주 좋을 것 같습니다.

      그래서 우선 제 같은 경우에는 openai api 와 kakaotrans를 섞어서 사용하고 있습니다. (플레이스토어 , 앱스토어 )

      하지만 BardAPI 에러 원인을 조금 더 정확히 파악하고 가능하면 Bard API도 같이 사용을 하고자 생각하고 있습니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      마법거북이 님의 최신 블로그

      더보기
      동영상 기고하기