๋ณธ๋ฌธ์œผ๋กœ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๋ฐ˜์‘ํ˜•

+ (25.10.13) ๋ฒ„์ „์ด ์—…๊ทธ๋ ˆ์ด๋“œ๋œ ์ดํ›„ API ํ˜ธ์ถœ ์ฃผ์†Œ๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ฒƒ ๊ฐ™๋‹ค.

ํ™•์ธํ•ด ๋ณด๋‹ˆ ๊ธฐ์กด APIํ‚ค๋„ ๋ชจ๋‘ ์‚ญ์ œ๋˜์—ˆ๋‹ค.

https://ai.google.dev/gemini-api/docs/quickstart?hl=ko

 

Gemini API ๋น ๋ฅธ ์‹œ์ž‘  |  Google AI for Developers

๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ Gemini API ์‹œ์ž‘ํ•˜๊ธฐ

ai.google.dev

ํ•ด๋‹น ์‚ฌ์ดํŠธ์— ์ ‘์†ํ•˜๋ฉด ์—ฐ๋™ ๋ฐฉ๋ฒ•์ด ์ž์„ธํžˆ ์•ˆ๋‚ด๋˜์–ด์žˆ๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜๋ฉด์„œ ์ƒˆ API ์ฃผ์†Œ๋กœ ํ…Œ์ŠคํŠธํ•ด๋ณด๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.


 

๋ญ”๊ฐ€ AI์ฑ—๋ด‡์„ ๋งŒ๋“ค์–ด๋ณด๊ณ ์‹ถ์–ด์„œ

๊ฐœ์ธ์ ์œผ๋กœ ๋งŒ๋“ค์–ด๋†“์€ ๋‹ค์ด์–ด๋ฆฌ์— ์–ด๋–ค๊ธฐ๋Šฅ์„ ๋„ฃ์–ด๋ณผ๊นŒํ•˜๋‹ค๊ฐ€

๋‹จ์–ด์žฅ์— ๋ฒˆ์—ญ๊ธฐ ๊ธฐ๋Šฅ์„ ๋„ฃ์œผ๋ฉด ์ข‹๊ฒ ๋‹ค ์ƒ๊ฐํ•ด์„œ

์ œ๋ฏธ๋‚˜์ด๊ฐ€ ๋ฒˆ์—ญํ•ด์ฃผ๋Š” ๋ฒˆ์—ญ๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด์•˜๋‹ค.

 

์›๋ž˜๋Š” GPT์จฉ์„ ์“ฐ๊ณ ์‹ถ์—ˆ์ง€๋งŒ ใ… ใ…  ์œ ๋ฃŒ๋กœ ๋ฐ”๋€Œ์–ด์„œ..

๋ฌด๋ฃŒํ‚ค๋ผ...์‘๋‹ต๋„๋А๋ฆฌ๊ณ ..๊ทธ๋ ‡์ง€๋งŒ..๋ฌด๋ฃŒ๋‹ˆ๊นŒ............

 

๋ฒˆ์—ญ๊ธฐ๋Š” ์•ˆ์— ๋‚ด์šฉ์ด ๋ถ€๋„๋Ÿฌ์šฐ๋‹ˆ๊นŒ ๋‹ค๋ฅธ ์˜ˆ์‹œ์ฝ”๋“œ๋กœ..

๊ฐ„๋‹จํ•˜๊ฒŒ ํ…Œ์ŠคํŠธํ•ด๋ณด๋Š” ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด๊ฒ ๋‹ค.

 

๋จผ์ € ์ œ๋ฏธ๋‚˜์ด apiํ‚ค๋ฅผ ๋ฐœ๊ธ‰๋ฐ›์•„์•ผ ํ•œ๋‹ค.

https://aistudio.google.com/apikey

 

๋กœ๊ทธ์ธ - Google ๊ณ„์ •

์ด๋ฉ”์ผ ๋˜๋Š” ํœด๋Œ€์ „ํ™”

accounts.google.com

์—ฌ๊ธฐ์„œ ๋กœ๊ทธ์ธ์„ ํ•œ ํ›„ APIํ‚ค๋ฅผ ๋ฌด๋ฃŒ๋กœ ๋ฐœ๊ธ‰๋ฐ›์œผ๋ฉด ๋˜๊ณ  ์ด๊ฒŒ ๋์ด ์•„๋‹ˆ๋ผ

์ œ๋ฏธ๋‚˜์ด api๋ฅผ ํ™œ์„ฑํ•ด์ฃผ์–ด์•ผ ๋น„๋กœ์†Œ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

https://console.cloud.google.com/apis/

 

Google ํด๋ผ์šฐ๋“œ ํ”Œ๋žซํผ

๋กœ๊ทธ์ธ Google ํด๋ผ์šฐ๋“œ ํ”Œ๋žซํผ์œผ๋กœ ์ด๋™

accounts.google.com

๊ทธ๊ฑด ์—ฌ๊ธฐ์—์„œ ๋กœ๊ทธ์ธ์„ ํ•œ๋‹ค์Œ

gemini api๋ฅผ ์„ ํƒํ•ด์ฃผ๊ณ 

์•„๋งˆ ๋ฒ„ํŠผ์ด ์žˆ์„ํ…๋ฐ ํ™œ์„ฑ์ด์˜€๋Š”์ง€ ์‚ฌ์šฉ์ด์—ˆ๋Š”์ง€ ๊ธฐ์–ต์ด ์•ˆ๋‚˜์ง€๋งŒ

๊ทธ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์ฃผ๋ฉด

์งœ์ž” ์ด๋ ‡๊ฒŒ ๋ณ€ํ•œ๋‹ค. 

์ด๋Ÿฌ๋ฉด ์ด์ œ gemini api๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๊ฒƒ์ด๋‹ค.

 

์•„๊นŒ ๋ดค๋˜ get api key์‚ฌ์ดํŠธ์— ๋“ค์–ด๊ฐ€๋ณด๋ฉด

curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent" \
  -H 'Content-Type: application/json' \
  -H 'X-goog-api-key: GEMINI_API_KEY' \
  -X POST \
  -d '{
    "contents": [
      {
        "parts": [
          {
            "text": "Explain how AI works in a few words"
          }
        ]
      }
    ]
  }'

์ด๋Ÿฐ์‹์œผ๋กœ ์‚ฌ์šฉ๋ฐฉ๋ฒ•์ด ๋‚˜์™€์žˆ๋‹ค.

๊ทธ๋Œ€๋กœ postman์—์„œ ํ™•์ธํ•ด๋ณด๋ฉด ์‘๋‹ต์ด ์–ด๋–ป๊ฒŒ ์˜ค๋Š”์ง€ ์•Œ์ˆ˜์žˆ์„๊ฒƒ์ด๋‹ค.

์ž์ด์ œ api ํ‚ค๋กœ ์ œ๋ฏธ๋‚˜์ด์™€ ์–˜๊ธฐ๋„ ํ•ด๋ดค๊ฒ ๋‹ค ๋ณธ๊ฒฉ์ ์œผ๋กœ ์—ฐ๋™์„ ํ•ด๋ณด์ž.

 

build.gradle์— ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

implementation 'org.springframework.boot:spring-boot-starter-webflux'

Spring ๋น„๋™๊ธฐ HTTP ํด๋ผ์ด์–ธํŠธ๋‹ค.

๋Œ€์ถฉ ์ด๋Ÿฐ ์—ญํ• ์ด๋ผ๊ณ  ํ•œ๋‹ค.

spring-boot-starter-webflux๋Š” WebClient ๋ฅผ ์“ฐ๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ์˜์กด์„ฑ
REST API ์š”์ฒญ์„ ๋น„๋™๊ธฐ๋กœ ๋‚ ๋ฆฌ๊ณ , ์‘๋‹ต์„ ์ด๋ฒคํŠธ์ฒ˜๋Ÿผ ๋ฐ›์Œ (์„ฑ๋Šฅ ํšจ์œจ์ )

 

application.properties์—๋„ api key๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.

# Gemini KEY
gemini.api.key=YOUR_KEY

 

GeminiSimpleController

@RestController
public class GeminiSimpleController {
    private final WebClient webClient = WebClient.create("https://generativelanguage.googleapis.com");

    @Value("${gemini.api.key}")
    private String apiKey;

    @PostMapping("/gemini/simple")
    public Mono<String> callGemini(@RequestBody String input) {
        Map<String, Object> body = Map.of(
            "contents", List.of(Map.of(
                "parts", List.of(Map.of("text", "๊ฐ„๋‹จํžˆ ๋Œ€๋‹ตํ•ด์ค˜:\n\n" + input))
            ))
        );

        return webClient.post()
            .uri(uriBuilder -> uriBuilder
                .path("/v1beta/models/gemini-1.5-flash-latest:generateContent")
                .queryParam("key", apiKey)
                .build())
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(body)
            .retrieve()
            .bodyToMono(String.class);
    }
}

 

TestController

package org.example.test.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class TestController {

    @GetMapping("/test")
    public String test() {
        return "test";
    }
}

 

test.html (ํƒ€์ž„๋ฆฌํ”„)

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Gemini ํ…Œ์ŠคํŠธ</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="/css/output.css" rel="stylesheet" th:href="@{/css/output.css}">
</head>
<body class="bg-gray-100 min-h-screen p-6">

<div class="max-w-md mx-auto bg-white p-6 rounded shadow space-y-4">
    <h1 class="text-xl font-bold text-center text-gray-800">Gemini ๊ฐ„๋‹จ ํ…Œ์ŠคํŠธ</h1>
    
    <!-- ์ž…๋ ฅ ํผ -->
    <form id="chat-form" class="flex gap-2">
        <input type="text" id="chat-input" class="flex-1 border rounded p-2" placeholder="์งˆ๋ฌธ ์ž…๋ ฅ..." required>
        <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">๋ณด๋‚ด๊ธฐ</button>
    </form>
    
    <!-- ์ฑ„ํŒ… ๋ฐ•์Šค -->
    <div id="chat-box" class="h-64 overflow-y-auto border rounded p-3 bg-gray-50 space-y-2 text-sm">
        <!-- ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ๋จ -->
    </div>
</div>

<script th:inline="javascript">
    const form = document.getElementById("chat-form");
    const input = document.getElementById("chat-input");
    const chatBox = document.getElementById("chat-box");
    
    form.addEventListener("submit", async (e) => {
        e.preventDefault();
        
        const userMsg = input.value.trim();
        if (!userMsg) return;
        
        appendMessage("๋‚˜", userMsg);
        input.value = "";
        
        try {
            const res = await fetch(/*[[${'/gemini/simple'}]]*/ "/gemini/simple", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(userMsg)
            });
            
            const data = await res.text();
            appendMessage("Gemini", data);
        } catch (err) {
            appendMessage("Gemini", "(์—๋Ÿฌ ๋ฐœ์ƒ: " + err.message + ")");
        }
    });
    
    function appendMessage(sender, message) {
        const div = document.createElement("div");
        div.innerHTML = `<strong>${sender}:</strong> <div class="whitespace-pre-line">${message}</div>`;
        chatBox.appendChild(div);
        chatBox.scrollTop = chatBox.scrollHeight;
    }
</script>

</body>
</html>

 

๊ฒฐ๊ณผํ™”๋ฉด

๋งŒ์•ฝ text๋งŒ ํŒŒ์‹ฑํ•ด์„œ ์•ˆ๋…•ํ•˜์„ธ์š”!๋งŒ ๊น”๋”ํ•˜๊ฒŒ ์ถ”์ถœํ•˜๊ณ ์‹ถ์œผ๋ฉด

const data = await res.text();
let reply = "(์‘๋‹ต ํ˜•์‹์ด ์ด์ƒํ•ฉ๋‹ˆ๋‹ค)";
try {
    const json = JSON.parse(data);
    const text = json.candidates?.[0]?.content?.parts?.[0]?.text;
    if (text) reply = text;
} catch (err) {
    reply = "(JSON ํŒŒ์‹ฑ ์‹คํŒจ)";
}
appendMessage("Gemini", reply);

์ด๋Ÿฐ์‹์œผ๋กœ ํŒŒ์‹ฑํ•ด์„œ ๋„ฃ์œผ๋ฉด ๋œ๋‹ค~

 

 

๋ฐ˜์‘ํ˜•