์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿค– Gemini API๋กœ ์ž๋™์š”์•ฝยท์ž๋™๋ถ„๋ฅ˜ ์—์ด์ „ํŠธ ๊ตฌ์ถ•ํ•˜๊ธฐ

๐Ÿค– Gemini API๋กœ ์ž๋™์š”์•ฝยท์ž๋™๋ถ„๋ฅ˜ ์—์ด์ „ํŠธ ๊ตฌ์ถ•ํ•˜๊ธฐ

AI๊ฐ€ ์•Œ์•„์„œ ์ •๋ฆฌํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•ด์ฃผ๋Š” ๋˜‘๋˜‘ํ•œ ๋น„์„œ ๋งŒ๋“ค๊ธฐ ๐Ÿš€

๐ŸŽฏ ์™œ ์ง€๊ธˆ Gemini API์ธ๊ฐ€?

์•ˆ๋…•! ์˜ค๋Š˜์€ ์ •๋ง ์‹ค์šฉ์ ์ด๋ฉด์„œ๋„ ์žฌ๋ฏธ์žˆ๋Š” ์ฃผ์ œ๋ฅผ ๊ฐ€์ง€๊ณ  ์™”์–ด. ๋ฐ”๋กœ Gemini API๋ฅผ ํ™œ์šฉํ•œ ์ž๋™์š”์•ฝยท์ž๋™๋ถ„๋ฅ˜ ์—์ด์ „ํŠธ ๊ตฌ์ถ•์ด์•ผ. ๐Ÿ˜Š

์š”์ฆ˜ ์ •๋ณด์˜ ํ™์ˆ˜ ์†์—์„œ ์‚ด๊ณ  ์žˆ์ž–์•„? ๋งค์ผ ์Ÿ์•„์ง€๋Š” ๋‰ด์Šค ๊ธฐ์‚ฌ, ์—…๋ฌด ๋ฌธ์„œ, ๊ณ ๊ฐ ๋ฌธ์˜, SNS ํ”ผ๋“œ... ์ด ๋ชจ๋“  ๊ฑธ ์ผ์ผ์ด ์ฝ๊ณ  ์ •๋ฆฌํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜๋Š” ๊ฑด ์ •๋ง ์‹œ๊ฐ„ ๋‚ญ๋น„์•ผ. ๊ทธ๋ž˜์„œ ๋“ฑ์žฅํ•œ ๊ฒŒ ๋ฐ”๋กœ AI ์—์ด์ „ํŠธ์ธ๋ฐ, ํŠนํžˆ ๊ตฌ๊ธ€์˜ Gemini API๋Š” 2024๋…„ ๋“ค์–ด์„œ ์—„์ฒญ๋‚œ ๋ฐœ์ „์„ ์ด๋ค˜์–ด.

Gemini API์˜ ๊ฐ€์žฅ ํฐ ์žฅ์ ์€ ๋ญ˜๊นŒ? ๋ฐ”๋กœ ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ฒ˜๋ฆฌ ๋Šฅ๋ ฅ์ด์•ผ. ํ…์ŠคํŠธ๋Š” ๋ฌผ๋ก ์ด๊ณ  ์ด๋ฏธ์ง€, ๋™์˜์ƒ, ์˜ค๋””์˜ค๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑฐ์ง€. ๊ฒŒ๋‹ค๊ฐ€ OpenAI์˜ GPT ์‹œ๋ฆฌ์ฆˆ์— ๋น„ํ•ด ๊ฐ€๊ฒฉ๋„ ํ•ฉ๋ฆฌ์ ์ด๊ณ , ๊ตฌ๊ธ€ ์ƒํƒœ๊ณ„์™€์˜ ํ†ตํ•ฉ๋„ ๋งค๋„๋Ÿฌ์›Œ. ๐ŸŽจ

๐Ÿ’ก ์‹ค์ œ ํ™œ์šฉ ์‚ฌ๋ก€ ํ•œ ์Šคํƒ€ํŠธ์—…์€ Gemini API๋ฅผ ํ™œ์šฉํ•ด ํ•˜๋ฃจ ํ‰๊ท  500๊ฑด์˜ ๊ณ ๊ฐ ๋ฌธ์˜๋ฅผ ์ž๋™์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ  ์š”์•ฝํ•˜๋Š” ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ–ˆ์–ด. ๊ทธ ๊ฒฐ๊ณผ ๊ณ ๊ฐ ์‘๋Œ€ ์‹œ๊ฐ„์ด 70% ๋‹จ์ถ•๋˜์—ˆ๊ณ , ์ง์›๋“ค์€ ๋” ์ค‘์š”ํ•œ ์—…๋ฌด์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋์ง€. ์ด๊ฒŒ ๋ฐ”๋กœ AI ์—์ด์ „ํŠธ์˜ ํž˜์ด์•ผ!
Gemini API ์ž๋™ํ™” ์›Œํฌํ”Œ๋กœ์šฐ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ๋ฌธ์„œ/์ด๋ฉ”์ผ/ ๊ณ ๊ฐ๋ฌธ์˜ Gemini API ์ž๋™์š”์•ฝ ์ž๋™๋ถ„๋ฅ˜ ์นดํ…Œ๊ณ ๋ฆฌ A ๊ธด๊ธ‰ ๋ฌธ์˜ ์นดํ…Œ๊ณ ๋ฆฌ B ์ผ๋ฐ˜ ๋ฌธ์˜ ์นดํ…Œ๊ณ ๋ฆฌ C ํ”ผ๋“œ๋ฐฑ ์ž๋™ํ™”๋œ ์ง€๋Šฅํ˜• ๋ฌธ์„œ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ

๐Ÿ”ง Gemini API ์‹œ์ž‘ํ•˜๊ธฐ: ๊ธฐ๋ณธ ์„ค์ •

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ? ๋จผ์ € Gemini API๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋ณธ ์„ค์ •๋ถ€ํ„ฐ ์•Œ์•„๋ณด์ž. ์ƒ๊ฐ๋ณด๋‹ค ๊ฐ„๋‹จํ•˜๋‹ˆ๊นŒ ๊ฑฑ์ •ํ•˜์ง€ ๋งˆ! ๐Ÿ˜Ž

1๋‹จ๊ณ„: API ํ‚ค ๋ฐœ๊ธ‰๋ฐ›๊ธฐ

๊ฐ€์žฅ ๋จผ์ € ํ•ด์•ผ ํ•  ์ผ์€ ๊ตฌ๊ธ€ AI Studio์—์„œ API ํ‚ค๋ฅผ ๋ฐœ๊ธ‰๋ฐ›๋Š” ๊ฑฐ์•ผ.

๋ฐœ๊ธ‰ ์ ˆ์ฐจ:
โ‘  Google AI Studio (makersuite.google.com/app/apikey) ์ ‘์†
โ‘ก ๊ตฌ๊ธ€ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ
โ‘ข "Get API Key" ๋ฒ„ํŠผ ํด๋ฆญ
โ‘ฃ ์ƒˆ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ๋˜๋Š” ๊ธฐ์กด ํ”„๋กœ์ ํŠธ ์„ ํƒ
โ‘ค API ํ‚ค ๋ณต์‚ฌ ๋ฐ ์•ˆ์ „ํ•œ ๊ณณ์— ๋ณด๊ด€

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฑด, API ํ‚ค๋Š” ์ ˆ๋Œ€ ๊ณต๊ฐœ ์ €์žฅ์†Œ์— ์˜ฌ๋ฆฌ๋ฉด ์•ˆ ๋ผ. ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋‚˜ ๋ณ„๋„์˜ ์„ค์ • ํŒŒ์ผ๋กœ ๊ด€๋ฆฌํ•ด์•ผ ํ•ด. ๐Ÿ’พ

โš ๏ธ ๋ณด์•ˆ ์ฃผ์˜์‚ฌํ•ญ API ํ‚ค๊ฐ€ ์œ ์ถœ๋˜๋ฉด ๋ˆ„๊ตฐ๊ฐ€ ๋‹น์‹ ์˜ ๊ณ„์ •์œผ๋กœ ๋ฌด์ œํ•œ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์–ด. ์ด๋Š” ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋น„์šฉ ์ฒญ๊ตฌ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋‹ˆ, ๋ฐ˜๋“œ์‹œ .env ํŒŒ์ผ์ด๋‚˜ ์‹œํฌ๋ฆฟ ๋งค๋‹ˆ์ €๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ด€๋ฆฌํ•˜์ž!
2๋‹จ๊ณ„: ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ตฌ์ถ•

Gemini API๋Š” ๋‹ค์–‘ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋ฅผ ์ง€์›ํ•ด. Python, JavaScript, Go, Java ๋“ฑ๋“ฑ... ์˜ค๋Š˜์€ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” Python์„ ๊ธฐ์ค€์œผ๋กœ ์„ค๋ช…ํ• ๊ฒŒ.

ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ์„ค์น˜:

pip install google-generativeai
pip install python-dotenv
pip install pandas  # ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์šฉ
pip install asyncio  # ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์šฉ

๊ธฐ๋ณธ ์„ค์ • ์ฝ”๋“œ๋Š” ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•˜๋ฉด ๋ผ:

import google.generativeai as genai
import os
from dotenv import load_dotenv

# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
load_dotenv()

# API ํ‚ค ์„ค์ •
GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
genai.configure(api_key=GOOGLE_API_KEY)

# ๋ชจ๋ธ ์ดˆ๊ธฐํ™”
model = genai.GenerativeModel('gemini-pro')

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ธฐ๋ณธ ์„ค์ •์€ ๋! ์ •๋ง ๊ฐ„๋‹จํ•˜์ง€? ๐ŸŽ‰


๐Ÿ“ ์ž๋™์š”์•ฝ ์—์ด์ „ํŠธ ๊ตฌ์ถ•ํ•˜๊ธฐ

์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์ž๋™์š”์•ฝ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์–ด๋ณด์ž. ์ž๋™์š”์•ฝ์€ ๊ธด ํ…์ŠคํŠธ๋ฅผ ํ•ต์‹ฌ ๋‚ด์šฉ๋งŒ ์ถ”์ถœํ•ด์„œ ์งง๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฑฐ์•ผ. ๋‰ด์Šค ๊ธฐ์‚ฌ, ๋ณด๊ณ ์„œ, ํšŒ์˜๋ก ๋“ฑ ๋‹ค์–‘ํ•œ ๊ณณ์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

๊ธฐ๋ณธ ์š”์•ฝ ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ

๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์š”์•ฝ ํ•จ์ˆ˜๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์ž:

def summarize_text(text, max_length=200):
    """
    ํ…์ŠคํŠธ๋ฅผ ์š”์•ฝํ•˜๋Š” ํ•จ์ˆ˜
    
    Args:
        text: ์š”์•ฝํ•  ์›๋ณธ ํ…์ŠคํŠธ
        max_length: ์š”์•ฝ๋ฌธ์˜ ์ตœ๋Œ€ ๊ธธ์ด (๋‹จ์–ด ์ˆ˜)
    
    Returns:
        ์š”์•ฝ๋œ ํ…์ŠคํŠธ
    """
    prompt = f"""
    ๋‹ค์Œ ํ…์ŠคํŠธ๋ฅผ {max_length}์ž ์ด๋‚ด๋กœ ํ•ต์‹ฌ ๋‚ด์šฉ๋งŒ ์š”์•ฝํ•ด์ฃผ์„ธ์š”.
    ์š”์•ฝํ•  ๋•Œ๋Š” ๋‹ค์Œ ๊ทœ์น™์„ ๋”ฐ๋ผ์ฃผ์„ธ์š”:
    1. ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ์šฐ์„ ์ ์œผ๋กœ ํฌํ•จ
    2. ๊ฐ๊ด€์ ์ด๊ณ  ์ค‘๋ฆฝ์ ์ธ ํ†ค ์œ ์ง€
    3. ๋ถˆํ•„์š”ํ•œ ์ˆ˜์‹์–ด ์ œ๊ฑฐ
    4. ๋ช…ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•œ ๋ฌธ์žฅ ์‚ฌ์šฉ
    
    ํ…์ŠคํŠธ:
    {text}
    
    ์š”์•ฝ:
    """
    
    response = model.generate_content(prompt)
    return response.text

์ด ํ•จ์ˆ˜๋Š” ๊ธฐ๋ณธ์ ์ธ ์š”์•ฝ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด. ํ•˜์ง€๋งŒ ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋” ์ •๊ตํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ด. ์˜ˆ๋ฅผ ๋“ค์–ด ์—๋Ÿฌ ํ•ธ๋“ค๋ง, ํ† ํฐ ์ œํ•œ ๊ด€๋ฆฌ, ์žฌ์‹œ๋„ ๋กœ์ง ๊ฐ™์€ ๊ฑฐ ๋ง์ด์•ผ.

๊ณ ๊ธ‰ ์š”์•ฝ ์‹œ์Šคํ…œ ๊ตฌ์ถ•

์ด์ œ ์ข€ ๋” ์‹ค์šฉ์ ์ธ ์š”์•ฝ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž. ์ด ์‹œ์Šคํ…œ์€ ๋‹ค์–‘ํ•œ ์š”์•ฝ ์Šคํƒ€์ผ์„ ์ง€์›ํ•˜๊ณ , ๊ธด ๋ฌธ์„œ๋„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด:

class AdvancedSummarizer:
    def __init__(self, model_name='gemini-pro'):
        self.model = genai.GenerativeModel(model_name)
        self.max_input_tokens = 30000  # Gemini Pro์˜ ํ† ํฐ ์ œํ•œ
        
    def chunk_text(self, text, chunk_size=5000):
        """๊ธด ํ…์ŠคํŠธ๋ฅผ ์ฒญํฌ๋กœ ๋‚˜๋ˆ„๊ธฐ"""
        words = text.split()
        chunks = []
        current_chunk = []
        current_length = 0
        
        for word in words:
            current_chunk.append(word)
            current_length += len(word) + 1
            
            if current_length >= chunk_size:
                chunks.append(' '.join(current_chunk))
                current_chunk = []
                current_length = 0
        
        if current_chunk:
            chunks.append(' '.join(current_chunk))
        
        return chunks
    
    def summarize(self, text, style='balanced', length='medium'):
        """
        ๋‹ค์–‘ํ•œ ์Šคํƒ€์ผ๋กœ ํ…์ŠคํŠธ ์š”์•ฝ
        
        Args:
            text: ์š”์•ฝํ•  ํ…์ŠคํŠธ
            style: 'balanced', 'bullet', 'executive', 'technical'
            length: 'short', 'medium', 'long'
        """
        length_map = {
            'short': 100,
            'medium': 300,
            'long': 500
        }
        
        style_instructions = {
            'balanced': '๊ท ํ˜•์žกํžŒ ์š”์•ฝ๋ฌธ์„ ์ž‘์„ฑํ•˜๋˜, ๋ชจ๋“  ์ฃผ์š” ํฌ์ธํŠธ๋ฅผ ํฌํ•จํ•˜์„ธ์š”.',
            'bullet': 'ํ•ต์‹ฌ ๋‚ด์šฉ์„ ๋ถˆ๋ฆฟ ํฌ์ธํŠธ ํ˜•์‹์œผ๋กœ ์ •๋ฆฌํ•˜์„ธ์š”.',
            'executive': '๊ฒฝ์˜์ง„์„ ์œ„ํ•œ ์š”์•ฝ์œผ๋กœ, ๊ฒฐ๋ก ๊ณผ ์•ก์…˜ ์•„์ดํ…œ์„ ๊ฐ•์กฐํ•˜์„ธ์š”.',
            'technical': '๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์ „๋ฌธ๊ฐ€๋ฅผ ์œ„ํ•œ ์š”์•ฝ์„ ์ž‘์„ฑํ•˜์„ธ์š”.'
        }
        
        max_length = length_map.get(length, 300)
        style_instruction = style_instructions.get(style, style_instructions['balanced'])
        
        # ํ…์ŠคํŠธ๊ฐ€ ๋„ˆ๋ฌด ๊ธธ๋ฉด ์ฒญํฌ๋กœ ๋‚˜๋ˆ„๊ธฐ
        if len(text) > 10000:
            chunks = self.chunk_text(text)
            summaries = []
            
            for chunk in chunks:
                prompt = f"""
                {style_instruction}
                ์ตœ๋Œ€ {max_length}์ž๋กœ ์š”์•ฝํ•˜์„ธ์š”.
                
                ํ…์ŠคํŠธ:
                {chunk}
                """
                response = self.model.generate_content(prompt)
                summaries.append(response.text)
            
            # ์ฒญํฌ ์š”์•ฝ๋“ค์„ ๋‹ค์‹œ ์ข…ํ•ฉ ์š”์•ฝ
            combined = ' '.join(summaries)
            final_prompt = f"""
            ๋‹ค์Œ์€ ๊ธด ๋ฌธ์„œ์˜ ๋ถ€๋ถ„ ์š”์•ฝ๋“ค์ž…๋‹ˆ๋‹ค.
            ์ด๋ฅผ ํ•˜๋‚˜์˜ ์ผ๊ด€๋œ ์š”์•ฝ๋ฌธ์œผ๋กœ ํ†ตํ•ฉํ•˜์„ธ์š”.
            {style_instruction}
            ์ตœ๋Œ€ {max_length}์ž๋กœ ์ž‘์„ฑํ•˜์„ธ์š”.
            
            ๋ถ€๋ถ„ ์š”์•ฝ๋“ค:
            {combined}
            """
            final_response = self.model.generate_content(final_prompt)
            return final_response.text
        else:
            prompt = f"""
            {style_instruction}
            ์ตœ๋Œ€ {max_length}์ž๋กœ ์š”์•ฝํ•˜์„ธ์š”.
            
            ํ…์ŠคํŠธ:
            {text}
            """
            response = self.model.generate_content(prompt)
            return response.text
๐ŸŽฏ ์‹ค์ „ ํ™œ์šฉ ํŒ ์ด ๊ณ ๊ธ‰ ์š”์•ฝ ์‹œ์Šคํ…œ์€ ์‹ค์ œ๋กœ ์—ฌ๋Ÿฌ ๊ธฐ์—…์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด์ด์•ผ. ํŠนํžˆ ์ฒญํฌ ๋ถ„ํ•  ๋ฐฉ์‹์€ ๊ธด ๋ฌธ์„œ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ํ•„์ˆ˜์ ์ด์ง€. Gemini API์˜ ํ† ํฐ ์ œํ•œ์„ ์šฐํšŒํ•˜๋ฉด์„œ๋„ ์ „์ฒด ๋งฅ๋ฝ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ .

์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ํ”„๋ฆฌ๋žœ์„œ๋กœ ํ™œ๋™ํ•œ๋‹ค๋ฉด, ์ด๋Ÿฐ ์ž๋™์š”์•ฝ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•ด์ฃผ๋Š” ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•  ์ˆ˜๋„ ์žˆ์–ด. ๋งŽ์€ ๊ธฐ์—…๋“ค์ด ๋ฌธ์„œ ์ž๋™ํ™” ์†”๋ฃจ์…˜์„ ์ฐพ๊ณ  ์žˆ๊ฑฐ๋“ !
์š”์•ฝ ํ’ˆ์งˆ ํ–ฅ์ƒ ํ…Œํฌ๋‹‰

์š”์•ฝ์˜ ํ’ˆ์งˆ์„ ๋†’์ด๊ธฐ ์œ„ํ•œ ๋ช‡ ๊ฐ€์ง€ ๊ณ ๊ธ‰ ํ…Œํฌ๋‹‰์„ ์†Œ๊ฐœํ• ๊ฒŒ:

1. ์ปจํ…์ŠคํŠธ ํ”„๋ฆฌ์ง• (Context Priming)
์š”์•ฝํ•˜๊ธฐ ์ „์— ๋ฌธ์„œ์˜ ์ข…๋ฅ˜์™€ ๋ชฉ์ ์„ ๋ช…ํ™•ํžˆ ํ•˜๋ฉด ๋” ์ข‹์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์–ด:

def context_aware_summarize(text, document_type, audience):
    """
    ์ปจํ…์ŠคํŠธ๋ฅผ ๊ณ ๋ คํ•œ ์š”์•ฝ
    
    Args:
        text: ์š”์•ฝํ•  ํ…์ŠคํŠธ
        document_type: 'news', 'research', 'business', 'technical'
        audience: 'general', 'expert', 'executive'
    """
    context_map = {
        'news': '๋‰ด์Šค ๊ธฐ์‚ฌ',
        'research': '์—ฐ๊ตฌ ๋…ผ๋ฌธ',
        'business': '๋น„์ฆˆ๋‹ˆ์Šค ๋ฌธ์„œ',
        'technical': '๊ธฐ์ˆ  ๋ฌธ์„œ'
    }
    
    audience_map = {
        'general': '์ผ๋ฐ˜ ๋Œ€์ค‘',
        'expert': 'ํ•ด๋‹น ๋ถ„์•ผ ์ „๋ฌธ๊ฐ€',
        'executive': '์˜์‚ฌ๊ฒฐ์ •๊ถŒ์ž'
    }
    
    prompt = f"""
    ๋‹น์‹ ์€ {context_map.get(document_type, '๋ฌธ์„œ')} ์š”์•ฝ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
    {audience_map.get(audience, '๋…์ž')}๋ฅผ ๋Œ€์ƒ์œผ๋กœ ์š”์•ฝ๋ฌธ์„ ์ž‘์„ฑํ•˜์„ธ์š”.
    
    ์š”์•ฝ ์‹œ ๋‹ค์Œ์„ ๊ณ ๋ คํ•˜์„ธ์š”:
    - ๋Œ€์ƒ ๋…์ž์˜ ๋ฐฐ๊ฒฝ ์ง€์‹ ์ˆ˜์ค€
    - ๋ฌธ์„œ ์œ ํ˜•์— ๋งž๋Š” ํ•ต์‹ฌ ์ •๋ณด
    - ์ ์ ˆํ•œ ์ „๋ฌธ ์šฉ์–ด ์‚ฌ์šฉ ์ˆ˜์ค€
    
    ํ…์ŠคํŠธ:
    {text}
    
    ์š”์•ฝ:
    """
    
    response = model.generate_content(prompt)
    return response.text

2. ๋ฉ€ํ‹ฐํŒจ์Šค ์š”์•ฝ (Multi-pass Summarization)
ํ•œ ๋ฒˆ์— ์š”์•ฝํ•˜๋Š” ๋Œ€์‹  ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์น˜๋ฉด ๋” ์ •ํ™•ํ•œ ์š”์•ฝ์„ ์–ป์„ ์ˆ˜ ์žˆ์–ด:

def multipass_summarize(text):
    """
    3๋‹จ๊ณ„ ์š”์•ฝ ํ”„๋กœ์„ธ์Šค
    """
    # 1๋‹จ๊ณ„: ์ดˆ๋ฒŒ ์š”์•ฝ (๊ธธ๊ฒŒ)
    first_pass = f"""
    ๋‹ค์Œ ํ…์ŠคํŠธ์˜ ๋ชจ๋“  ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜์—ฌ 
    ์ƒ์„ธํ•˜๊ฒŒ ์š”์•ฝํ•˜์„ธ์š” (500์ž ๋‚ด์™ธ):
    
    {text}
    """
    first_summary = model.generate_content(first_pass).text
    
    # 2๋‹จ๊ณ„: ํ•ต์‹ฌ ์ถ”์ถœ
    second_pass = f"""
    ๋‹ค์Œ ์š”์•ฝ๋ฌธ์—์„œ ๊ฐ€์žฅ ํ•ต์‹ฌ์ ์ธ 3-5๊ฐœ์˜ ํฌ์ธํŠธ๋ฅผ ์ถ”์ถœํ•˜์„ธ์š”:
    
    {first_summary}
    """
    key_points = model.generate_content(second_pass).text
    
    # 3๋‹จ๊ณ„: ์ตœ์ข… ์š”์•ฝ
    third_pass = f"""
    ๋‹ค์Œ ํ•ต์‹ฌ ํฌ์ธํŠธ๋“ค์„ ๋ฐ”ํƒ•์œผ๋กœ 
    ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•œ ์ตœ์ข… ์š”์•ฝ๋ฌธ์„ ์ž‘์„ฑํ•˜์„ธ์š” (200์ž ๋‚ด์™ธ):
    
    {key_points}
    
    ์›๋ฌธ ์ฐธ์กฐ:
    {text[:500]}...
    """
    final_summary = model.generate_content(third_pass).text
    
    return {
        'final_summary': final_summary,
        'key_points': key_points,
        'detailed_summary': first_summary
    }

์ด ๋ฉ€ํ‹ฐํŒจ์Šค ๋ฐฉ์‹์€ ํŠนํžˆ ๋ณต์žกํ•œ ๊ธฐ์ˆ  ๋ฌธ์„œ๋‚˜ ๋ฒ•๋ฅ  ๋ฌธ์„œ๋ฅผ ์š”์•ฝํ•  ๋•Œ ํšจ๊ณผ์ ์ด์•ผ. ๋‹จ๊ณ„๋ณ„๋กœ ์ •๋ณด๋ฅผ ์ •์ œํ•˜๋ฉด์„œ ์ค‘์š”ํ•œ ๋‚ด์šฉ์ด ๋ˆ„๋ฝ๋˜๋Š” ๊ฑธ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐ŸŽฏ


๐Ÿ—‚๏ธ ์ž๋™๋ถ„๋ฅ˜ ์—์ด์ „ํŠธ ๊ตฌ์ถ•ํ•˜๊ธฐ

์ด์ œ ์ž๋™๋ถ„๋ฅ˜ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž! ์ž๋™๋ถ„๋ฅ˜๋Š” ํ…์ŠคํŠธ๋ฅผ ๋ฏธ๋ฆฌ ์ •์˜๋œ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ์ž๋™์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜๋Š” ๊ฑฐ์•ผ. ์ด๋ฉ”์ผ ํ•„ํ„ฐ๋ง, ๊ณ ๊ฐ ๋ฌธ์˜ ๋ผ์šฐํŒ…, ์ฝ˜ํ…์ธ  ํƒœ๊น… ๋“ฑ ์ •๋ง ๋‹ค์–‘ํ•˜๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿ“Š

๊ธฐ๋ณธ ๋ถ„๋ฅ˜ ์‹œ์Šคํ…œ

๋จผ์ € ๊ฐ„๋‹จํ•œ ๋ถ„๋ฅ˜ ํ•จ์ˆ˜๋ถ€ํ„ฐ ๋งŒ๋“ค์–ด๋ณด์ž:

def classify_text(text, categories):
    """
    ํ…์ŠคํŠธ๋ฅผ ์ฃผ์–ด์ง„ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ๋ถ„๋ฅ˜
    
    Args:
        text: ๋ถ„๋ฅ˜ํ•  ํ…์ŠคํŠธ
        categories: ๊ฐ€๋Šฅํ•œ ์นดํ…Œ๊ณ ๋ฆฌ ๋ฆฌ์ŠคํŠธ
    
    Returns:
        ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ (์นดํ…Œ๊ณ ๋ฆฌ, ์‹ ๋ขฐ๋„)
    """
    categories_str = ', '.join(categories)
    
    prompt = f"""
    ๋‹ค์Œ ํ…์ŠคํŠธ๋ฅผ ์•„๋ž˜ ์นดํ…Œ๊ณ ๋ฆฌ ์ค‘ ํ•˜๋‚˜๋กœ ๋ถ„๋ฅ˜ํ•˜์„ธ์š”.
    
    ์นดํ…Œ๊ณ ๋ฆฌ: {categories_str}
    
    ํ…์ŠคํŠธ:
    {text}
    
    ์‘๋‹ต ํ˜•์‹:
    ์นดํ…Œ๊ณ ๋ฆฌ: [์„ ํƒ๋œ ์นดํ…Œ๊ณ ๋ฆฌ]
    ์‹ ๋ขฐ๋„: [0-100 ์‚ฌ์ด์˜ ์ˆซ์ž]
    ์ด์œ : [๋ถ„๋ฅ˜ ๊ทผ๊ฑฐ๋ฅผ ๊ฐ„๋‹จํžˆ ์„ค๋ช…]
    """
    
    response = model.generate_content(prompt)
    return response.text

์ด๊ฑด ๊ธฐ๋ณธ์ ์ธ ๋ถ„๋ฅ˜์•ผ. ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ๋” ์ •๊ตํ•œ ์‹œ์Šคํ…œ์ด ํ•„์š”ํ•ด. ํŠนํžˆ ๋‹ค์ค‘ ๋ ˆ์ด๋ธ” ๋ถ„๋ฅ˜, ๊ณ„์ธต์  ๋ถ„๋ฅ˜, ์‹ ๋ขฐ๋„ ๊ธฐ๋ฐ˜ ํ•„ํ„ฐ๋ง ๊ฐ™์€ ๊ธฐ๋Šฅ๋“ค์ด ์ค‘์š”ํ•˜์ง€.

๊ณ ๊ธ‰ ๋ถ„๋ฅ˜ ์‹œ์Šคํ…œ

์ด์ œ ํ”„๋กœ๋•์…˜ ๋ ˆ๋ฒจ์˜ ๋ถ„๋ฅ˜ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž:

import json
from typing import List, Dict, Tuple
from dataclasses import dataclass

@dataclass
class ClassificationResult:
    """๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ๋ฅผ ๋‹ด๋Š” ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค"""
    primary_category: str
    confidence: float
    secondary_categories: List[Tuple[str, float]]
    reasoning: str
    tags: List[str]

class AdvancedClassifier:
    def __init__(self, model_name='gemini-pro'):
        self.model = genai.GenerativeModel(model_name)
        self.classification_history = []
        
    def classify(self, 
                text: str, 
                categories: List[str],
                multi_label: bool = False,
                min_confidence: float = 0.7) -> ClassificationResult:
        """
        ๊ณ ๊ธ‰ ํ…์ŠคํŠธ ๋ถ„๋ฅ˜
        
        Args:
            text: ๋ถ„๋ฅ˜ํ•  ํ…์ŠคํŠธ
            categories: ์นดํ…Œ๊ณ ๋ฆฌ ๋ฆฌ์ŠคํŠธ
            multi_label: ๋‹ค์ค‘ ๋ ˆ์ด๋ธ” ํ—ˆ์šฉ ์—ฌ๋ถ€
            min_confidence: ์ตœ์†Œ ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’
        """
        if multi_label:
            prompt = self._create_multilabel_prompt(text, categories)
        else:
            prompt = self._create_singlelabel_prompt(text, categories)
        
        response = self.model.generate_content(prompt)
        result = self._parse_classification_response(response.text)
        
        # ์‹ ๋ขฐ๋„ ํ•„ํ„ฐ๋ง
        if result.confidence < min_confidence:
            result.primary_category = "UNCERTAIN"
            result.reasoning += f" (์‹ ๋ขฐ๋„ {result.confidence}๊ฐ€ ์ž„๊ณ„๊ฐ’ {min_confidence}๋ณด๋‹ค ๋‚ฎ์Œ)"
        
        # ๋ถ„๋ฅ˜ ์ด๋ ฅ ์ €์žฅ
        self.classification_history.append({
            'text': text[:100],
            'result': result,
            'timestamp': datetime.now()
        })
        
        return result
    
    def _create_singlelabel_prompt(self, text: str, categories: List[str]) -> str:
        """๋‹จ์ผ ๋ ˆ์ด๋ธ” ๋ถ„๋ฅ˜ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ"""
        categories_desc = '\n'.join([f"- {cat}" for cat in categories])
        
        return f"""
        ๋‹ค์Œ ํ…์ŠคํŠธ๋ฅผ ๊ฐ€์žฅ ์ ํ•ฉํ•œ ํ•˜๋‚˜์˜ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ๋ถ„๋ฅ˜ํ•˜์„ธ์š”.
        
        ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์นดํ…Œ๊ณ ๋ฆฌ:
        {categories_desc}
        
        ํ…์ŠคํŠธ:
        {text}
        
        ๋‹ค์Œ JSON ํ˜•์‹์œผ๋กœ ์‘๋‹ตํ•˜์„ธ์š”:
        {{
            "primary_category": "์„ ํƒ๋œ ์นดํ…Œ๊ณ ๋ฆฌ",
            "confidence": 0.95,
            "reasoning": "๋ถ„๋ฅ˜ ๊ทผ๊ฑฐ",
            "tags": ["๊ด€๋ จ", "ํ‚ค์›Œ๋“œ", "๋ฆฌ์ŠคํŠธ"]
        }}
        """
    
    def _create_multilabel_prompt(self, text: str, categories: List[str]) -> str:
        """๋‹ค์ค‘ ๋ ˆ์ด๋ธ” ๋ถ„๋ฅ˜ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ"""
        categories_desc = '\n'.join([f"- {cat}" for cat in categories])
        
        return f"""
        ๋‹ค์Œ ํ…์ŠคํŠธ์— ํ•ด๋‹นํ•˜๋Š” ๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์„ ํƒํ•˜์„ธ์š”.
        ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์ ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
        
        ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์นดํ…Œ๊ณ ๋ฆฌ:
        {categories_desc}
        
        ํ…์ŠคํŠธ:
        {text}
        
        ๋‹ค์Œ JSON ํ˜•์‹์œผ๋กœ ์‘๋‹ตํ•˜์„ธ์š”:
        {{
            "primary_category": "๊ฐ€์žฅ ๊ด€๋ จ์„ฑ ๋†’์€ ์นดํ…Œ๊ณ ๋ฆฌ",
            "confidence": 0.95,
            "secondary_categories": [
                ["์นดํ…Œ๊ณ ๋ฆฌ2", 0.85],
                ["์นดํ…Œ๊ณ ๋ฆฌ3", 0.70]
            ],
            "reasoning": "๋ถ„๋ฅ˜ ๊ทผ๊ฑฐ",
            "tags": ["๊ด€๋ จ", "ํ‚ค์›Œ๋“œ", "๋ฆฌ์ŠคํŠธ"]
        }}
        """
    
    def _parse_classification_response(self, response_text: str) -> ClassificationResult:
        """์‘๋‹ต ํ…์ŠคํŠธ๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ ClassificationResult ๊ฐ์ฒด ์ƒ์„ฑ"""
        try:
            # JSON ์ถ”์ถœ (์‘๋‹ต์— ๋‹ค๋ฅธ ํ…์ŠคํŠธ๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์Œ)
            json_start = response_text.find('{')
            json_end = response_text.rfind('}') + 1
            json_str = response_text[json_start:json_end]
            
            data = json.loads(json_str)
            
            return ClassificationResult(
                primary_category=data.get('primary_category', 'UNKNOWN'),
                confidence=float(data.get('confidence', 0.0)),
                secondary_categories=data.get('secondary_categories', []),
                reasoning=data.get('reasoning', ''),
                tags=data.get('tags', [])
            )
        except Exception as e:
            # ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
            return ClassificationResult(
                primary_category='PARSE_ERROR',
                confidence=0.0,
                secondary_categories=[],
                reasoning=f'์‘๋‹ต ํŒŒ์‹ฑ ์‹คํŒจ: {str(e)}',
                tags=[]
            )
    
    def batch_classify(self, texts: List[str], categories: List[str]) -> List[ClassificationResult]:
        """์—ฌ๋Ÿฌ ํ…์ŠคํŠธ๋ฅผ ์ผ๊ด„ ๋ถ„๋ฅ˜"""
        results = []
        for text in texts:
            result = self.classify(text, categories)
            results.append(result)
        return results
    
    def get_classification_stats(self) -> Dict:
        """๋ถ„๋ฅ˜ ํ†ต๊ณ„ ๋ฐ˜ํ™˜"""
        if not self.classification_history:
            return {}
        
        category_counts = {}
        total_confidence = 0
        
        for item in self.classification_history:
            cat = item['result'].primary_category
            category_counts[cat] = category_counts.get(cat, 0) + 1
            total_confidence += item['result'].confidence
        
        return {
            'total_classifications': len(self.classification_history),
            'category_distribution': category_counts,
            'average_confidence': total_confidence / len(self.classification_history)
        }
๐Ÿ’ก ํ”„๋กœ ํŒ: ๊ณ„์ธต์  ๋ถ„๋ฅ˜ ๋ณต์žกํ•œ ๋ถ„๋ฅ˜ ์‹œ์Šคํ…œ์—์„œ๋Š” ๊ณ„์ธต์  ๋ถ„๋ฅ˜๊ฐ€ ๋งค์šฐ ์œ ์šฉํ•ด. ์˜ˆ๋ฅผ ๋“ค์–ด:

1๋‹จ๊ณ„: ๋Œ€๋ถ„๋ฅ˜ (๊ธฐ์ˆ /๋น„์ฆˆ๋‹ˆ์Šค/๋งˆ์ผ€ํŒ…)
2๋‹จ๊ณ„: ์ค‘๋ถ„๋ฅ˜ (๊ธฐ์ˆ  โ†’ ์›น๊ฐœ๋ฐœ/๋ชจ๋ฐ”์ผ/AI)
3๋‹จ๊ณ„: ์†Œ๋ถ„๋ฅ˜ (AI โ†’ ๋จธ์‹ ๋Ÿฌ๋‹/๋”ฅ๋Ÿฌ๋‹/NLP)

์ด๋ ‡๊ฒŒ ๋‹จ๊ณ„๋ณ„๋กœ ๋ถ„๋ฅ˜ํ•˜๋ฉด ๋” ์ •ํ™•ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์–ด!
์‹ค์ „ ์˜ˆ์ œ: ๊ณ ๊ฐ ๋ฌธ์˜ ์ž๋™ ๋ถ„๋ฅ˜ ์‹œ์Šคํ…œ

์ด์ œ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ณ ๊ฐ ๋ฌธ์˜ ์ž๋™ ๋ถ„๋ฅ˜ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž:

class CustomerInquiryClassifier:
    def __init__(self):
        self.classifier = AdvancedClassifier()
        
        # ๊ณ ๊ฐ ๋ฌธ์˜ ์นดํ…Œ๊ณ ๋ฆฌ ์ •์˜
        self.categories = {
            'URGENT': {
                'description': '๊ธด๊ธ‰ ๋ฌธ์˜ (์„œ๋น„์Šค ์žฅ์• , ๊ฒฐ์ œ ์˜ค๋ฅ˜ ๋“ฑ)',
                'priority': 1,
                'sla': '1์‹œ๊ฐ„ ์ด๋‚ด ์‘๋‹ต'
            },
            'TECHNICAL': {
                'description': '๊ธฐ์ˆ  ์ง€์› ๋ฌธ์˜',
                'priority': 2,
                'sla': '4์‹œ๊ฐ„ ์ด๋‚ด ์‘๋‹ต'
            },
            'BILLING': {
                'description': '๊ฒฐ์ œ ๋ฐ ์ฒญ๊ตฌ ๊ด€๋ จ',
                'priority': 2,
                'sla': '4์‹œ๊ฐ„ ์ด๋‚ด ์‘๋‹ต'
            },
            'GENERAL': {
                'description': '์ผ๋ฐ˜ ๋ฌธ์˜',
                'priority': 3,
                'sla': '24์‹œ๊ฐ„ ์ด๋‚ด ์‘๋‹ต'
            },
            'FEEDBACK': {
                'description': 'ํ”ผ๋“œ๋ฐฑ ๋ฐ ์ œ์•ˆ',
                'priority': 4,
                'sla': '48์‹œ๊ฐ„ ์ด๋‚ด ์‘๋‹ต'
            }
        }
        
    def classify_inquiry(self, inquiry_text: str, customer_info: Dict = None):
        """
        ๊ณ ๊ฐ ๋ฌธ์˜๋ฅผ ๋ถ„๋ฅ˜ํ•˜๊ณ  ์šฐ์„ ์ˆœ์œ„ ํ• ๋‹น
        
        Args:
            inquiry_text: ๋ฌธ์˜ ๋‚ด์šฉ
            customer_info: ๊ณ ๊ฐ ์ •๋ณด (์„ ํƒ์‚ฌํ•ญ)
        """
        # ๊ธฐ๋ณธ ๋ถ„๋ฅ˜
        categories_list = list(self.categories.keys())
        result = self.classifier.classify(
            inquiry_text, 
            categories_list,
            multi_label=True,
            min_confidence=0.6
        )
        
        # ์šฐ์„ ์ˆœ์œ„ ์ •๋ณด ์ถ”๊ฐ€
        category_info = self.categories.get(result.primary_category, {})
        
        # ๊ฐ์ • ๋ถ„์„ ์ถ”๊ฐ€
        sentiment = self._analyze_sentiment(inquiry_text)
        
        # ๊ธด๊ธ‰๋„ ์กฐ์ • (๋ถ€์ •์  ๊ฐ์ •์ด ๊ฐ•ํ•˜๋ฉด ์šฐ์„ ์ˆœ์œ„ ์ƒํ–ฅ)
        adjusted_priority = category_info.get('priority', 3)
        if sentiment['score'] < -0.5:
            adjusted_priority = max(1, adjusted_priority - 1)
        
        return {
            'classification': result,
            'category_info': category_info,
            'sentiment': sentiment,
            'adjusted_priority': adjusted_priority,
            'recommended_action': self._get_recommended_action(result, sentiment),
            'assigned_team': self._assign_team(result.primary_category)
        }
    
    def _analyze_sentiment(self, text: str) -> Dict:
        """๊ฐ์ • ๋ถ„์„"""
        prompt = f"""
        ๋‹ค์Œ ํ…์ŠคํŠธ์˜ ๊ฐ์ •์„ ๋ถ„์„ํ•˜์„ธ์š”.
        
        ํ…์ŠคํŠธ: {text}
        
        JSON ํ˜•์‹์œผ๋กœ ์‘๋‹ต:
        {{
            "sentiment": "positive/neutral/negative",
            "score": -1.0์—์„œ 1.0 ์‚ฌ์ด์˜ ์ˆซ์ž,
            "emotion": "์ฃผ์š” ๊ฐ์ • (์˜ˆ: ๋งŒ์กฑ, ๋ถˆ๋งŒ, ๊ฑฑ์ • ๋“ฑ)"
        }}
        """
        
        response = self.classifier.model.generate_content(prompt)
        try:
            json_start = response.text.find('{')
            json_end = response.text.rfind('}') + 1
            return json.loads(response.text[json_start:json_end])
        except:
            return {'sentiment': 'neutral', 'score': 0.0, 'emotion': 'unknown'}
    
    def _get_recommended_action(self, classification: ClassificationResult, sentiment: Dict) -> str:
        """์ถ”์ฒœ ์•ก์…˜ ์ƒ์„ฑ"""
        actions = {
            'URGENT': '์ฆ‰์‹œ ๋‹ด๋‹น์ž์—๊ฒŒ ์—์Šค์ปฌ๋ ˆ์ด์…˜',
            'TECHNICAL': '๊ธฐ์ˆ  ์ง€์›ํŒ€์— ํ‹ฐ์ผ“ ์ƒ์„ฑ',
            'BILLING': '๊ฒฐ์ œํŒ€์— ์ „๋‹ฌ ๋ฐ ๊ฒ€ํ†  ์š”์ฒญ',
            'GENERAL': '์ผ๋ฐ˜ ์ƒ๋‹ดํŒ€ ํ์— ์ถ”๊ฐ€',
            'FEEDBACK': '์ œํ’ˆํŒ€์— ํ”ผ๋“œ๋ฐฑ ์ „๋‹ฌ'
        }
        
        action = actions.get(classification.primary_category, '๊ฒ€ํ†  ํ•„์š”')
        
        if sentiment['sentiment'] == 'negative' and sentiment['score'] < -0.7:
            action = f"โš ๏ธ ๊ณ ๊ฐ ๋ถˆ๋งŒ ์ฃผ์˜ - {action}"
        
        return action
    
    def _assign_team(self, category: str) -> str:
        """๋‹ด๋‹น ํŒ€ ํ• ๋‹น"""
        team_mapping = {
            'URGENT': 'Emergency Response Team',
            'TECHNICAL': 'Technical Support Team',
            'BILLING': 'Billing & Finance Team',
            'GENERAL': 'Customer Service Team',
            'FEEDBACK': 'Product Team'
        }
        return team_mapping.get(category, 'General Queue')

์ด ์‹œ์Šคํ…œ์€ ์‹ค์ œ ๊ณ ๊ฐ ์„œ๋น„์Šค ์„ผํ„ฐ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์ด์•ผ. ์ž๋™ ๋ถ„๋ฅ˜, ์šฐ์„ ์ˆœ์œ„ ํ• ๋‹น, ๊ฐ์ • ๋ถ„์„, ํŒ€ ๋ฐฐ์ •๊นŒ์ง€ ๋ชจ๋‘ ์ž๋™ํ™”๋˜์–ด ์žˆ์ง€! ๐Ÿ˜Š

๐Ÿ“ˆ ์‹ค์ œ ์„ฑ๊ณผ ๋ฐ์ดํ„ฐ ํ•œ ์ค‘๊ฒฌ ๊ธฐ์—…์—์„œ ์ด์™€ ์œ ์‚ฌํ•œ ์‹œ์Šคํ…œ์„ ๋„์ž…ํ•œ ๊ฒฐ๊ณผ:

โ€ข ๋ฌธ์˜ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ 65% ๊ฐ์†Œ
โ€ข ๊ณ ๊ฐ ๋งŒ์กฑ๋„ 23% ํ–ฅ์ƒ
โ€ข ์ง์› ์—…๋ฌด ํšจ์œจ 40% ์ฆ๊ฐ€
โ€ข ๊ธด๊ธ‰ ๋ฌธ์˜ ๋Œ€์‘ ์‹œ๊ฐ„ 80% ๋‹จ์ถ•

์ด๋Ÿฐ ์„ฑ๊ณผ๋ฅผ ๋‚ผ ์ˆ˜ ์žˆ์—ˆ๋˜ ๊ฑด AI๊ฐ€ ๋ฐ˜๋ณต์ ์ธ ๋ถ„๋ฅ˜ ์ž‘์—…์„ ์ž๋™ํ™”ํ•ด์ฃผ๋ฉด์„œ, ์ง์›๋“ค์ด ์‹ค์ œ ๋ฌธ์ œ ํ•ด๊ฒฐ์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด์•ผ.

๐Ÿ”„ ์š”์•ฝ๊ณผ ๋ถ„๋ฅ˜๋ฅผ ๊ฒฐํ•ฉํ•œ ํ†ตํ•ฉ ์—์ด์ „ํŠธ

์ด์ œ ์š”์•ฝ๊ณผ ๋ถ„๋ฅ˜๋ฅผ ํ•˜๋‚˜๋กœ ํ•ฉ์นœ ๊ฐ•๋ ฅํ•œ ํ†ตํ•ฉ ์—์ด์ „ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! ์‹ค์ œ ์—…๋ฌด์—์„œ๋Š” ์ด ๋‘ ๊ธฐ๋Šฅ์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๊ฑฐ๋“ . ๐Ÿ“ฑ

ํ†ตํ•ฉ ์—์ด์ „ํŠธ ์•„ํ‚คํ…์ฒ˜

ํ†ตํ•ฉ ์—์ด์ „ํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์›Œํฌํ”Œ๋กœ์šฐ๋กœ ๋™์ž‘ํ•ด:

class UnifiedAgent:
    """์š”์•ฝ๊ณผ ๋ถ„๋ฅ˜๋ฅผ ํ†ตํ•ฉํ•œ AI ์—์ด์ „ํŠธ"""
    
    def __init__(self):
        self.summarizer = AdvancedSummarizer()
        self.classifier = AdvancedClassifier()
        self.model = genai.GenerativeModel('gemini-pro')
        
    async def process_document(self, 
                              text: str, 
                              categories: List[str],
                              summarize: bool = True,
                              classify: bool = True,
                              extract_entities: bool = False) -> Dict:
        """
        ๋ฌธ์„œ๋ฅผ ์ข…ํ•ฉ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
        
        Args:
            text: ์ฒ˜๋ฆฌํ•  ํ…์ŠคํŠธ
            categories: ๋ถ„๋ฅ˜ ์นดํ…Œ๊ณ ๋ฆฌ
            summarize: ์š”์•ฝ ์ˆ˜ํ–‰ ์—ฌ๋ถ€
            classify: ๋ถ„๋ฅ˜ ์ˆ˜ํ–‰ ์—ฌ๋ถ€
            extract_entities: ๊ฐœ์ฒด๋ช… ์ถ”์ถœ ์—ฌ๋ถ€
        
        Returns:
            ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ ๋”•์…”๋„ˆ๋ฆฌ
        """
        results = {
            'original_length': len(text),
            'processing_timestamp': datetime.now().isoformat()
        }
        
        # ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํƒœ์Šคํฌ ๋ฆฌ์ŠคํŠธ
        tasks = []
        
        if summarize:
            summary_task = self._async_summarize(text)
            tasks.append(('summary', summary_task))
        
        if classify:
            classification_task = self._async_classify(text, categories)
            tasks.append(('classification', classification_task))
        
        if extract_entities:
            entities_task = self._async_extract_entities(text)
            tasks.append(('entities', entities_task))
        
        # ๋ชจ๋“  ํƒœ์Šคํฌ ์‹คํ–‰
        for task_name, task in tasks:
            result = await task
            results[task_name] = result
        
        # ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ
        results['metadata'] = self._generate_metadata(results)
        
        return results
    
    async def _async_summarize(self, text: str) -> Dict:
        """๋น„๋™๊ธฐ ์š”์•ฝ"""
        try:
            summary = self.summarizer.summarize(text, style='balanced', length='medium')
            key_points = self._extract_key_points(text)
            
            return {
                'summary': summary,
                'key_points': key_points,
                'compression_ratio': len(summary) / len(text)
            }
        except Exception as e:
            return {'error': str(e)}
    
    async def _async_classify(self, text: str, categories: List[str]) -> Dict:
        """๋น„๋™๊ธฐ ๋ถ„๋ฅ˜"""
        try:
            result = self.classifier.classify(text, categories, multi_label=True)
            return {
                'primary_category': result.primary_category,
                'confidence': result.confidence,
                'all_categories': result.secondary_categories,
                'tags': result.tags
            }
        except Exception as e:
            return {'error': str(e)}
    
    async def _async_extract_entities(self, text: str) -> Dict:
        """๋น„๋™๊ธฐ ๊ฐœ์ฒด๋ช… ์ถ”์ถœ"""
        prompt = f"""
        ๋‹ค์Œ ํ…์ŠคํŠธ์—์„œ ์ฃผ์š” ๊ฐœ์ฒด๋ช…์„ ์ถ”์ถœํ•˜์„ธ์š”:
        - ์ธ๋ฌผ (PERSON)
        - ์กฐ์ง (ORGANIZATION)
        - ์žฅ์†Œ (LOCATION)
        - ๋‚ ์งœ (DATE)
        - ๊ธˆ์•ก (MONEY)
        
        ํ…์ŠคํŠธ:
        {text}
        
        JSON ํ˜•์‹์œผ๋กœ ์‘๋‹ต:
        {{
            "persons": ["์ด๋ฆ„1", "์ด๋ฆ„2"],
            "organizations": ["์กฐ์ง1", "์กฐ์ง2"],
            "locations": ["์žฅ์†Œ1", "์žฅ์†Œ2"],
            "dates": ["๋‚ ์งœ1", "๋‚ ์งœ2"],
            "money": ["๊ธˆ์•ก1", "๊ธˆ์•ก2"]
        }}
        """
        
        try:
            response = self.model.generate_content(prompt)
            json_start = response.text.find('{')
            json_end = response.text.rfind('}') + 1
            return json.loads(response.text[json_start:json_end])
        except Exception as e:
            return {'error': str(e)}
    
    def _extract_key_points(self, text: str) -> List[str]:
        """ํ•ต์‹ฌ ํฌ์ธํŠธ ์ถ”์ถœ"""
        prompt = f"""
        ๋‹ค์Œ ํ…์ŠคํŠธ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ 3-5๊ฐœ์˜ ํ•ต์‹ฌ ํฌ์ธํŠธ๋ฅผ ์ถ”์ถœํ•˜์„ธ์š”.
        ๊ฐ ํฌ์ธํŠธ๋Š” ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์ž‘์„ฑํ•˜์„ธ์š”.
        
        ํ…์ŠคํŠธ:
        {text}
        
        ํ•ต์‹ฌ ํฌ์ธํŠธ:
        """
        
        response = self.model.generate_content(prompt)
        points = [line.strip() for line in response.text.split('\n') if line.strip()]
        return points[:5]
    
    def _generate_metadata(self, results: Dict) -> Dict:
        """์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ"""
        metadata = {
            'processing_time': datetime.now().isoformat(),
            'components_used': []
        }
        
        if 'summary' in results:
            metadata['components_used'].append('summarization')
            metadata['summary_length'] = len(results['summary'].get('summary', ''))
        
        if 'classification' in results:
            metadata['components_used'].append('classification')
            metadata['primary_category'] = results['classification'].get('primary_category')
        
        if 'entities' in results:
            metadata['components_used'].append('entity_extraction')
            metadata['entity_count'] = sum(len(v) for v in results['entities'].values() if isinstance(v, list))
        
        return metadata
์‹ค์ „ ํ™œ์šฉ: ๋‰ด์Šค ๊ธฐ์‚ฌ ์ž๋™ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ

์ด์ œ ์‹ค์ œ๋กœ ๋‰ด์Šค ๊ธฐ์‚ฌ๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž:

class NewsProcessingAgent:
    """๋‰ด์Šค ๊ธฐ์‚ฌ ์ž๋™ ์ฒ˜๋ฆฌ ์—์ด์ „ํŠธ"""
    
    def __init__(self):
        self.agent = UnifiedAgent()
        self.news_categories = [
            '์ •์น˜', '๊ฒฝ์ œ', '์‚ฌํšŒ', '๋ฌธํ™”', '๊ตญ์ œ', 
            '๊ณผํ•™๊ธฐ์ˆ ', '์Šคํฌ์ธ ', '์—ฐ์˜ˆ'
        ]
        
    async def process_news_article(self, article: Dict) -> Dict:
        """
        ๋‰ด์Šค ๊ธฐ์‚ฌ ์ข…ํ•ฉ ์ฒ˜๋ฆฌ
        
        Args:
            article: {'title': str, 'content': str, 'source': str, 'date': str}
        """
        full_text = f"{article['title']}\n\n{article['content']}"
        
        # ํ†ตํ•ฉ ์—์ด์ „ํŠธ๋กœ ์ฒ˜๋ฆฌ
        result = await self.agent.process_document(
            text=full_text,
            categories=self.news_categories,
            summarize=True,
            classify=True,
            extract_entities=True
        )
        
        # ๋‰ด์Šค ํŠนํ™” ๋ถ„์„ ์ถ”๊ฐ€
        result['news_analysis'] = await self._analyze_news_specific(article)
        
        # ๊ด€๋ จ ๊ธฐ์‚ฌ ์ถ”์ฒœ
        result['related_topics'] = self._generate_related_topics(result)
        
        # SEO ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ
        result['seo_metadata'] = self._generate_seo_metadata(article, result)
        
        return result
    
    async def _analyze_news_specific(self, article: Dict) -> Dict:
        """๋‰ด์Šค ํŠนํ™” ๋ถ„์„"""
        prompt = f"""
        ๋‹ค์Œ ๋‰ด์Šค ๊ธฐ์‚ฌ๋ฅผ ๋ถ„์„ํ•˜์„ธ์š”:
        
        ์ œ๋ชฉ: {article['title']}
        ๋‚ด์šฉ: {article['content'][:500]}...
        
        ๋‹ค์Œ ํ•ญ๋ชฉ์„ JSON ํ˜•์‹์œผ๋กœ ๋ถ„์„:
        {{
            "objectivity_score": 0-100 (๊ฐ๊ด€์„ฑ ์ ์ˆ˜),
            "controversy_level": "low/medium/high",
            "impact_assessment": "๋ถ„์„ ๋‚ด์šฉ",
            "fact_check_needed": true/false,
            "target_audience": "์ฃผ์š” ๋…์ž์ธต"
        }}
        """
        
        response = self.agent.model.generate_content(prompt)
        try:
            json_start = response.text.find('{')
            json_end = response.text.rfind('}') + 1
            return json.loads(response.text[json_start:json_end])
        except:
            return {}
    
    def _generate_related_topics(self, result: Dict) -> List[str]:
        """๊ด€๋ จ ํ† ํ”ฝ ์ƒ์„ฑ"""
        topics = []
        
        # ๋ถ„๋ฅ˜ ์นดํ…Œ๊ณ ๋ฆฌ์—์„œ ํ† ํ”ฝ ์ถ”์ถœ
        if 'classification' in result:
            topics.append(result['classification'].get('primary_category'))
            topics.extend(result['classification'].get('tags', []))
        
        # ๊ฐœ์ฒด๋ช…์—์„œ ํ† ํ”ฝ ์ถ”์ถœ
        if 'entities' in result:
            entities = result['entities']
            topics.extend(entities.get('organizations', [])[:3])
            topics.extend(entities.get('persons', [])[:3])
        
        return list(set(topics))[:10]
    
    def _generate_seo_metadata(self, article: Dict, result: Dict) -> Dict:
        """SEO ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ"""
        summary = result.get('summary', {}).get('summary', '')
        tags = result.get('classification', {}).get('tags', [])
        
        return {
            'meta_description': summary[:160] if summary else article['title'],
            'keywords': ', '.join(tags[:10]),
            'og_title': article['title'],
            'og_description': summary[:200] if summary else '',
            'article_section': result.get('classification', {}).get('primary_category', '๋‰ด์Šค')
        }
๐Ÿš€ ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ ํ†ตํ•ฉ ์—์ด์ „ํŠธ๋ฅผ ์šด์˜ํ•  ๋•Œ๋Š” ๋‹ค์Œ ์‚ฌํ•ญ๋“ค์„ ๊ณ ๋ คํ•ด์•ผ ํ•ด:

1. ์บ์‹ฑ ์ „๋žต
๋™์ผํ•œ ๋ฌธ์„œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋„๋ก ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•ด. Redis๋‚˜ Memcached๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ข‹์•„.

2. ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ
์—ฌ๋Ÿฌ ๋ฌธ์„œ๋ฅผ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ๋•Œ๋Š” ๋ฐฐ์น˜ API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋น„์šฉ๊ณผ ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์–ด.

3. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
asyncio๋ฅผ ํ™œ์šฉํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋กœ ์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋™์‹œ์— ์ˆ˜ํ–‰ํ•˜๋ฉด ์ „์ฒด ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์„ ํฌ๊ฒŒ ์ค„์ผ ์ˆ˜ ์žˆ์–ด.

4. ์—๋Ÿฌ ํ•ธ๋“ค๋ง
API ํ˜ธ์ถœ ์‹คํŒจ์— ๋Œ€๋น„ํ•œ ์žฌ์‹œ๋„ ๋กœ์ง๊ณผ ํด๋ฐฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๋ฐ˜๋“œ์‹œ ๊ตฌํ˜„ํ•ด์•ผ ํ•ด.

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™”์™€ ๋น„์šฉ ๊ด€๋ฆฌ

Gemini API๋ฅผ ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์„ฑ๋Šฅ ์ตœ์ ํ™”์™€ ๋น„์šฉ ๊ด€๋ฆฌ๊ฐ€ ์ •๋ง ์ค‘์š”ํ•ด. ์ œ๋Œ€๋กœ ๊ด€๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฉด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋น„์šฉ์ด ์ฒญ๊ตฌ๋  ์ˆ˜ ์žˆ๊ฑฐ๋“ ! ๐Ÿ’ฐ

ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰ ์ตœ์ ํ™”

Gemini API๋Š” ํ† ํฐ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ณผ๊ธˆ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์ด๋Š” ๊ฒŒ ๋น„์šฉ ์ ˆ๊ฐ์˜ ํ•ต์‹ฌ์ด์•ผ:

class TokenOptimizer:
    """ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰ ์ตœ์ ํ™” ์œ ํ‹ธ๋ฆฌํ‹ฐ"""
    
    def __init__(self):
        self.token_limits = {
            'gemini-pro': 30720,  # ์ž…๋ ฅ ํ† ํฐ ์ œํ•œ
            'gemini-pro-vision': 12288
        }
        
    def estimate_tokens(self, text: str) -> int:
        """ํ† ํฐ ์ˆ˜ ์ถ”์ • (๋Œ€๋žต์ )"""
        # ์˜์–ด: 1 ํ† ํฐ โ‰ˆ 4 ๊ธ€์ž
        # ํ•œ๊ธ€: 1 ํ† ํฐ โ‰ˆ 2-3 ๊ธ€์ž
        korean_chars = sum(1 for c in text if ord(c) >= 0xAC00 and ord(c) <= 0xD7A3)
        other_chars = len(text) - korean_chars
        
        estimated_tokens = (korean_chars / 2.5) + (other_chars / 4)
        return int(estimated_tokens)
    
    def optimize_prompt(self, prompt: str, max_tokens: int = 2000) -> str:
        """ํ”„๋กฌํ”„ํŠธ ์ตœ์ ํ™”"""
        current_tokens = self.estimate_tokens(prompt)
        
        if current_tokens <= max_tokens:
            return prompt
        
        # ํ† ํฐ ์ˆ˜๊ฐ€ ์ดˆ๊ณผํ•˜๋ฉด ํ…์ŠคํŠธ ์••์ถ•
        compression_ratio = max_tokens / current_tokens
        target_length = int(len(prompt) * compression_ratio * 0.9)  # ์•ˆ์ „ ๋งˆ์ง„
        
        # ์ค‘์š”ํ•œ ๋ถ€๋ถ„ ์œ ์ง€ํ•˜๋ฉด์„œ ์••์ถ•
        lines = prompt.split('\n')
        optimized_lines = []
        current_length = 0
        
        for line in lines:
            if current_length + len(line) <= target_length:
                optimized_lines.append(line)
                current_length += len(line)
            else:
                break
        
        return '\n'.join(optimized_lines)
    
    def chunk_large_text(self, text: str, chunk_size: int = 5000) -> List[str]:
        """ํฐ ํ…์ŠคํŠธ๋ฅผ ์ฒญํฌ๋กœ ๋ถ„ํ• """
        chunks = []
        words = text.split()
        current_chunk = []
        current_size = 0
        
        for word in words:
            word_tokens = self.estimate_tokens(word)
            
            if current_size + word_tokens > chunk_size:
                chunks.append(' '.join(current_chunk))
                current_chunk = [word]
                current_size = word_tokens
            else:
                current_chunk.append(word)
                current_size += word_tokens
        
        if current_chunk:
            chunks.append(' '.join(current_chunk))
        
        return chunks
์บ์‹ฑ ์ „๋žต

๋™์ผํ•œ ์š”์ฒญ์„ ๋ฐ˜๋ณตํ•˜์ง€ ์•Š๋„๋ก ์บ์‹ฑ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด:

import hashlib
import json
from typing import Optional
import redis

class CacheManager:
    """API ์‘๋‹ต ์บ์‹ฑ ๊ด€๋ฆฌ์ž"""
    
    def __init__(self, redis_host='localhost', redis_port=6379):
        self.redis_client = redis.Redis(
            host=redis_host, 
            port=redis_port, 
            decode_responses=True
        )
        self.default_ttl = 3600 * 24  # 24์‹œ๊ฐ„
        
    def generate_cache_key(self, text: str, operation: str, params: Dict = None) -> str:
        """์บ์‹œ ํ‚ค ์ƒ์„ฑ"""
        cache_data = {
            'text': text[:500],  # ํ…์ŠคํŠธ ์ผ๋ถ€๋งŒ ์‚ฌ์šฉ
            'operation': operation,
            'params': params or {}
        }
        
        cache_string = json.dumps(cache_data, sort_keys=True)
        return f"gemini:{operation}:{hashlib.md5(cache_string.encode()).hexdigest()}"
    
    def get_cached_result(self, cache_key: str) -> Optional[Dict]:
        """์บ์‹œ๋œ ๊ฒฐ๊ณผ ์กฐํšŒ"""
        cached = self.redis_client.get(cache_key)
        if cached:
            return json.loads(cached)
        return None
    
    def cache_result(self, cache_key: str, result: Dict, ttl: int = None):
        """๊ฒฐ๊ณผ ์บ์‹ฑ"""
        ttl = ttl or self.default_ttl
        self.redis_client.setex(
            cache_key,
            ttl,
            json.dumps(result)
        )
    
    def invalidate_cache(self, pattern: str):
        """์บ์‹œ ๋ฌดํšจํ™”"""
        keys = self.redis_client.keys(pattern)
        if keys:
            self.redis_client.delete(*keys)

class CachedAgent:
    """์บ์‹ฑ์„ ์ ์šฉํ•œ ์—์ด์ „ํŠธ"""
    
    def __init__(self):
        self.agent = UnifiedAgent()
        self.cache = CacheManager()
        self.cache_hit_count = 0
        self.cache_miss_count = 0
        
    async def process_with_cache(self, text: str, categories: List[str]) -> Dict:
        """์บ์‹ฑ์„ ํ™œ์šฉํ•œ ์ฒ˜๋ฆฌ"""
        # ์บ์‹œ ํ‚ค ์ƒ์„ฑ
        cache_key = self.cache.generate_cache_key(
            text, 
            'unified_process',
            {'categories': categories}
        )
        
        # ์บ์‹œ ํ™•์ธ
        cached_result = self.cache.get_cached_result(cache_key)
        if cached_result:
            self.cache_hit_count += 1
            cached_result['from_cache'] = True
            return cached_result
        
        # ์บ์‹œ ๋ฏธ์Šค - ์‹ค์ œ ์ฒ˜๋ฆฌ
        self.cache_miss_count += 1
        result = await self.agent.process_document(
            text, 
            categories,
            summarize=True,
            classify=True
        )
        
        # ๊ฒฐ๊ณผ ์บ์‹ฑ
        result['from_cache'] = False
        self.cache.cache_result(cache_key, result)
        
        return result
    
    def get_cache_stats(self) -> Dict:
        """์บ์‹œ ํ†ต๊ณ„"""
        total = self.cache_hit_count + self.cache_miss_count
        hit_rate = (self.cache_hit_count / total * 100) if total > 0 else 0
        
        return {
            'cache_hits': self.cache_hit_count,
            'cache_misses': self.cache_miss_count,
            'hit_rate': f"{hit_rate:.2f}%",
            'total_requests': total
        }
๐Ÿ’ก ์บ์‹ฑ ํšจ๊ณผ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ์บ์‹ฑ์„ ์ ์šฉํ•œ ๊ฒฐ๊ณผ:

โ€ข API ํ˜ธ์ถœ ํšŸ์ˆ˜ 60% ๊ฐ์†Œ
โ€ข ์‘๋‹ต ์‹œ๊ฐ„ 85% ๋‹จ์ถ•
โ€ข ์›” ๋น„์šฉ 55% ์ ˆ๊ฐ
โ€ข ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ ํ–ฅ์ƒ

ํŠนํžˆ ๋ฐ˜๋ณต์ ์ธ ์š”์ฒญ์ด ๋งŽ์€ ํ™˜๊ฒฝ์—์„œ๋Š” ์บ์‹ฑ์ด ํ•„์ˆ˜์•ผ!
๋น„์šฉ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ

API ์‚ฌ์šฉ๋Ÿ‰๊ณผ ๋น„์šฉ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ์‹œ์Šคํ…œ๋„ ์ค‘์š”ํ•ด:

class CostMonitor:
    """API ๋น„์šฉ ๋ชจ๋‹ˆํ„ฐ๋ง"""
    
    def __init__(self):
        self.pricing = {
            'gemini-pro': {
                'input': 0.00025,   # 1K ํ† ํฐ๋‹น $0.00025
                'output': 0.0005    # 1K ํ† ํฐ๋‹น $0.0005
            }
        }
        self.usage_log = []
        
    def log_api_call(self, 
                    model: str,
                    input_tokens: int,
                    output_tokens: int,
                    operation: str):
        """API ํ˜ธ์ถœ ๋กœ๊น…"""
        cost = self.calculate_cost(model, input_tokens, output_tokens)
        
        log_entry = {
            'timestamp': datetime.now().isoformat(),
            'model': model,
            'operation': operation,
            'input_tokens': input_tokens,
            'output_tokens': output_tokens,
            'cost': cost
        }
        
        self.usage_log.append(log_entry)
        
        # ๋น„์šฉ ์ž„๊ณ„๊ฐ’ ์ฒดํฌ
        self._check_cost_threshold()
        
    def calculate_cost(self, model: str, input_tokens: int, output_tokens: int) -> float:
        """๋น„์šฉ ๊ณ„์‚ฐ"""
        pricing = self.pricing.get(model, self.pricing['gemini-pro'])
        
        input_cost = (input_tokens / 1000) * pricing['input']
        output_cost = (output_tokens / 1000) * pricing['output']
        
        return input_cost + output_cost
    
    def get_daily_stats(self) -> Dict:
        """์ผ์ผ ํ†ต๊ณ„"""
        today = datetime.now().date()
        today_logs = [
            log for log in self.usage_log 
            if datetime.fromisoformat(log['timestamp']).date() == today
        ]
        
        if not today_logs:
            return {}
        
        total_cost = sum(log['cost'] for log in today_logs)
        total_calls = len(today_logs)
        total_input_tokens = sum(log['input_tokens'] for log in today_logs)
        total_output_tokens = sum(log['output_tokens'] for log in today_logs)
        
        return {
            'date': today.isoformat(),
            'total_calls': total_calls,
            'total_cost': f"${total_cost:.4f}",
            'total_input_tokens': total_input_tokens,
            'total_output_tokens': total_output_tokens,
            'average_cost_per_call': f"${total_cost/total_calls:.4f}" if total_calls > 0 else "$0"
        }
    
    def get_monthly_projection(self) -> Dict:
        """์›”๊ฐ„ ๋น„์šฉ ์˜ˆ์ธก"""
        daily_stats = self.get_daily_stats()
        if not daily_stats:
            return {}
        
        daily_cost = float(daily_stats['total_cost'].replace('$', ''))
        monthly_projection = daily_cost * 30
        
        return {
            'daily_average': f"${daily_cost:.2f}",
            'monthly_projection': f"${monthly_projection:.2f}",
            'annual_projection': f"${monthly_projection * 12:.2f}"
        }
    
    def _check_cost_threshold(self):
        """๋น„์šฉ ์ž„๊ณ„๊ฐ’ ์ฒดํฌ"""
        daily_stats = self.get_daily_stats()
        if not daily_stats:
            return
        
        daily_cost = float(daily_stats['total_cost'].replace('$', ''))
        
        # ์ผ์ผ ๋น„์šฉ์ด $10๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ๊ฒฝ๊ณ 
        if daily_cost > 10:
            print(f"โš ๏ธ ๊ฒฝ๊ณ : ์˜ค๋Š˜ API ๋น„์šฉ์ด ${daily_cost:.2f}์ž…๋‹ˆ๋‹ค!")
        
        # ์›”๊ฐ„ ์˜ˆ์ƒ ๋น„์šฉ์ด $200๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ๊ฒฝ๊ณ 
        monthly = self.get_monthly_projection()
        if monthly:
            monthly_cost = float(monthly['monthly_projection'].replace('$', ''))
            if monthly_cost > 200:
                print(f"โš ๏ธ ๊ฒฝ๊ณ : ์›”๊ฐ„ ์˜ˆ์ƒ ๋น„์šฉ์ด ${monthly_cost:.2f}์ž…๋‹ˆ๋‹ค!")

๐Ÿ›ก๏ธ ์—๋Ÿฌ ํ•ธ๋“ค๋ง๊ณผ ์•ˆ์ •์„ฑ

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ๊ฐ€ ํ•ญ์ƒ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด. ๊ฒฌ๊ณ ํ•œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง ์‹œ์Šคํ…œ์ด ํ•„์ˆ˜์•ผ! ๐Ÿ”ง

์žฌ์‹œ๋„ ๋กœ์ง ๊ตฌํ˜„

API ํ˜ธ์ถœ์ด ์‹คํŒจํ–ˆ์„ ๋•Œ ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„ํ•˜๋Š” ์‹œ์Šคํ…œ:

import time
from functools import wraps
from typing import Callable, Any

class RetryHandler:
    """์žฌ์‹œ๋„ ํ•ธ๋“ค๋Ÿฌ"""
    
    def __init__(self, max_retries=3, base_delay=1, max_delay=60):
        self.max_retries = max_retries
        self.base_delay = base_delay
        self.max_delay = max_delay
        
    def exponential_backoff(self, attempt: int) -> float:
        """์ง€์ˆ˜ ๋ฐฑ์˜คํ”„ ๊ณ„์‚ฐ"""
        delay = min(self.base_delay * (2 ** attempt), self.max_delay)
        return delay
    
    def retry_with_backoff(self, func: Callable) -> Callable:
        """์žฌ์‹œ๋„ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ"""
        @wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            last_exception = None
            
            for attempt in range(self.max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    
                    if attempt < self.max_retries - 1:
                        delay = self.exponential_backoff(attempt)
                        print(f"โš ๏ธ ์‹œ๋„ {attempt + 1} ์‹คํŒจ. {delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
                        time.sleep(delay)
                    else:
                        print(f"โŒ ๋ชจ๋“  ์žฌ์‹œ๋„ ์‹คํŒจ: {str(e)}")
            
            raise last_exception
        
        return wrapper

class RobustAgent:
    """๊ฒฌ๊ณ ํ•œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ๊ฐ€์ง„ ์—์ด์ „ํŠธ"""
    
    def __init__(self):
        self.model = genai.GenerativeModel('gemini-pro')
        self.retry_handler = RetryHandler(max_retries=3)
        self.error_log = []
        
    @RetryHandler(max_retries=3).retry_with_backoff
    def safe_generate(self, prompt: str) -> str:
        """์•ˆ์ „ํ•œ ์ฝ˜ํ…์ธ  ์ƒ์„ฑ"""
        try:
            response = self.model.generate_content(prompt)
            
            # ์‘๋‹ต ๊ฒ€์ฆ
            if not response or not response.text:
                raise ValueError("๋นˆ ์‘๋‹ต ์ˆ˜์‹ ")
            
            return response.text
            
        except Exception as e:
            self._log_error('generate', str(e), prompt[:100])
            raise
    
    def safe_summarize(self, text: str, fallback: str = None) -> str:
        """ํด๋ฐฑ์„ ๊ฐ€์ง„ ์•ˆ์ „ํ•œ ์š”์•ฝ"""
        try:
            prompt = f"๋‹ค์Œ ํ…์ŠคํŠธ๋ฅผ 200์ž ์ด๋‚ด๋กœ ์š”์•ฝํ•˜์„ธ์š”:\n\n{text}"
            return self.safe_generate(prompt)
            
        except Exception as e:
            print(f"โš ๏ธ ์š”์•ฝ ์‹คํŒจ, ํด๋ฐฑ ์‚ฌ์šฉ: {str(e)}")
            
            # ํด๋ฐฑ ์ „๋žต
            if fallback:
                return fallback
            else:
                # ๊ฐ„๋‹จํ•œ ์ถ”์ถœ ์š”์•ฝ์œผ๋กœ ํด๋ฐฑ
                sentences = text.split('.')
                return '. '.join(sentences[:3]) + '.'
    
    def safe_classify(self, text: str, categories: List[str]) -> Dict:
        """ํด๋ฐฑ์„ ๊ฐ€์ง„ ์•ˆ์ „ํ•œ ๋ถ„๋ฅ˜"""
        try:
            prompt = f"""
            ๋‹ค์Œ ํ…์ŠคํŠธ๋ฅผ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ๋ถ„๋ฅ˜ํ•˜์„ธ์š”: {', '.join(categories)}
            
            ํ…์ŠคํŠธ: {text}
            
            JSON ํ˜•์‹์œผ๋กœ ์‘๋‹ต:
            {{"category": "์„ ํƒ๋œ ์นดํ…Œ๊ณ ๋ฆฌ", "confidence": 0.95}}
            """
            
            response = self.safe_generate(prompt)
            
            # JSON ํŒŒ์‹ฑ
            json_start = response.find('{')
            json_end = response.rfind('}') + 1
            result = json.loads(response[json_start:json_end])
            
            return result
            
        except Exception as e:
            print(f"โš ๏ธ ๋ถ„๋ฅ˜ ์‹คํŒจ, ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜: {str(e)}")
            
            # ํด๋ฐฑ: ํ‚ค์›Œ๋“œ ๊ธฐ๋ฐ˜ ๊ฐ„๋‹จํ•œ ๋ถ„๋ฅ˜
            return self._fallback_classify(text, categories)
    
    def _fallback_classify(self, text: str, categories: List[str]) -> Dict:
        """ํด๋ฐฑ ๋ถ„๋ฅ˜ (ํ‚ค์›Œ๋“œ ๊ธฐ๋ฐ˜)"""
        text_lower = text.lower()
        
        # ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ‚ค์›Œ๋“œ ๋งค์นญ ์ ์ˆ˜
        scores = {}
        for category in categories:
            # ๊ฐ„๋‹จํ•œ ํ‚ค์›Œ๋“œ ๋งค์นญ
            score = text_lower.count(category.lower())
            scores[category] = score
        
        # ๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜์˜ ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ
        best_category = max(scores, key=scores.get) if scores else categories[0]
        
        return {
            'category': best_category,
            'confidence': 0.5,  # ๋‚ฎ์€ ์‹ ๋ขฐ๋„ ํ‘œ์‹œ
            'method': 'fallback'
        }
    
    def _log_error(self, operation: str, error: str, context: str):
        """์—๋Ÿฌ ๋กœ๊น…"""
        self.error_log.append({
            'timestamp': datetime.now().isoformat(),
            'operation': operation,
            'error': error,
            'context': context
        })
    
    def get_error_stats(self) -> Dict:
        """์—๋Ÿฌ ํ†ต๊ณ„"""
        if not self.error_log:
            return {'total_errors': 0}
        
        error_types = {}
        for log in self.error_log:
            error_type = log['error'].split(':')[0]
            error_types[error_type] = error_types.get(error_type, 0) + 1
        
        return {
            'total_errors': len(self.error_log),
            'error_types': error_types,
            'recent_errors': self.error_log[-5:]
        }
โš ๏ธ ํ”„๋กœ๋•์…˜ ์ฒดํฌ๋ฆฌ์ŠคํŠธ ์‹ค์ œ ์„œ๋น„์Šค์— ๋ฐฐํฌํ•˜๊ธฐ ์ „์— ๋ฐ˜๋“œ์‹œ ํ™•์ธํ•ด์•ผ ํ•  ์‚ฌํ•ญ๋“ค:

โœ… API ํ‚ค ๋ณด์•ˆ (ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ)
โœ… ์žฌ์‹œ๋„ ๋กœ์ง ๊ตฌํ˜„
โœ… ์—๋Ÿฌ ๋กœ๊น… ์‹œ์Šคํ…œ
โœ… ๋น„์šฉ ๋ชจ๋‹ˆํ„ฐ๋ง
โœ… ์บ์‹ฑ ์‹œ์Šคํ…œ
โœ… ํ† ํฐ ์ œํ•œ ๊ด€๋ฆฌ
โœ… ํด๋ฐฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜
โœ… ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง
โœ… ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ ์ˆ˜์ง‘
โœ… ์ •๊ธฐ์ ์ธ ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ

๐ŸŽจ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์ฝ˜ํ…์ธ  ํ๋ ˆ์ด์…˜ ํ”Œ๋žซํผ

์ด์ œ ์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋ชจ๋“  ๊ฑธ ํ™œ์šฉํ•ด์„œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! ๋ฐ”๋กœ AI ๊ธฐ๋ฐ˜ ์ฝ˜ํ…์ธ  ํ๋ ˆ์ด์…˜ ํ”Œ๋žซํผ์ด์•ผ. ๐Ÿ“ฐ

ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

์ด ํ”Œ๋žซํผ์€ ๋‹ค์Œ ๊ธฐ๋Šฅ๋“ค์„ ์ œ๊ณตํ•ด:

โ€ข ๋‹ค์–‘ํ•œ ์†Œ์Šค์—์„œ ์ฝ˜ํ…์ธ  ์ˆ˜์ง‘
โ€ข ์ž๋™ ์š”์•ฝ ๋ฐ ๋ถ„๋ฅ˜
โ€ข ์‚ฌ์šฉ์ž ๊ด€์‹ฌ์‚ฌ ๊ธฐ๋ฐ˜ ์ถ”์ฒœ
โ€ข ํŠธ๋ Œ๋“œ ๋ถ„์„
โ€ข ๊ฐœ์ธํ™”๋œ ๋‰ด์Šค๋ ˆํ„ฐ ์ƒ์„ฑ

class ContentCurationPlatform:
    """AI ๊ธฐ๋ฐ˜ ์ฝ˜ํ…์ธ  ํ๋ ˆ์ด์…˜ ํ”Œ๋žซํผ"""
    
    def __init__(self):
        self.agent = UnifiedAgent()
        self.classifier = AdvancedClassifier()
        self.cache = CacheManager()
        self.cost_monitor = CostMonitor()
        
        # ์ฝ˜ํ…์ธ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (์‹ค์ œ๋กœ๋Š” PostgreSQL ๋“ฑ ์‚ฌ์šฉ)
        self.content_db = []
        self.user_preferences = {}
        
    async def ingest_content(self, content: Dict) -> Dict:
        """
        ์ฝ˜ํ…์ธ  ์ˆ˜์ง‘ ๋ฐ ์ฒ˜๋ฆฌ
        
        Args:
            content: {
                'title': str,
                'body': str,
                'source': str,
                'url': str,
                'published_date': str
            }
        """
        # 1. ์ค‘๋ณต ์ฒดํฌ
        if self._is_duplicate(content):
            return {'status': 'duplicate', 'content_id': None}
        
        # 2. ์ฝ˜ํ…์ธ  ์ฒ˜๋ฆฌ
        full_text = f"{content['title']}\n\n{content['body']}"
        
        processed = await self.agent.process_document(
            text=full_text,
            categories=[
                '๊ธฐ์ˆ ', '๋น„์ฆˆ๋‹ˆ์Šค', '๊ณผํ•™', '๋ฌธํ™”', '์ •์น˜', 
                '๊ฒฝ์ œ', '์Šคํฌ์ธ ', '๊ฑด๊ฐ•', '๊ต์œก', '์—”ํ„ฐํ…Œ์ธ๋จผํŠธ'
            ],
            summarize=True,
            classify=True,
            extract_entities=True
        )
        
        # 3. ํ’ˆ์งˆ ์ ์ˆ˜ ๊ณ„์‚ฐ
        quality_score = self._calculate_quality_score(content, processed)
        
        # 4. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ
        content_entry = {
            'id': self._generate_id(),
            'original': content,
            'processed': processed,
            'quality_score': quality_score,
            'ingested_at': datetime.now().isoformat(),
            'view_count': 0,
            'engagement_score': 0
        }
        
        self.content_db.append(content_entry)
        
        # 5. ๋น„์šฉ ๋กœ๊น…
        self.cost_monitor.log_api_call(
            'gemini-pro',
            input_tokens=len(full_text) // 4,
            output_tokens=len(str(processed)) // 4,
            operation='content_ingestion'
        )
        
        return {
            'status': 'success',
            'content_id': content_entry['id'],
            'summary': processed.get('summary', {}).get('summary'),
            'category': processed.get('classification', {}).get('primary_category')
        }
    
    def _is_duplicate(self, content: Dict) -> bool:
        """์ค‘๋ณต ์ฝ˜ํ…์ธ  ์ฒดํฌ"""
        # ์ œ๋ชฉ ๊ธฐ๋ฐ˜ ๊ฐ„๋‹จํ•œ ์ค‘๋ณต ์ฒดํฌ
        title = content['title'].lower()
        for item in self.content_db:
            if item['original']['title'].lower() == title:
                return True
        return False
    
    def _calculate_quality_score(self, content: Dict, processed: Dict) -> float:
        """์ฝ˜ํ…์ธ  ํ’ˆ์งˆ ์ ์ˆ˜ ๊ณ„์‚ฐ"""
        score = 0.5  # ๊ธฐ๋ณธ ์ ์ˆ˜
        
        # ์š”์•ฝ ํ’ˆ์งˆ
        if 'summary' in processed:
            summary = processed['summary']
            if summary.get('compression_ratio', 0) < 0.5:
                score += 0.1
        
        # ๋ถ„๋ฅ˜ ์‹ ๋ขฐ๋„
        if 'classification' in processed:
            confidence = processed['classification'].get('confidence', 0)
            score += confidence * 0.2
        
        # ๊ฐœ์ฒด๋ช… ์ถ”์ถœ ํ’ˆ์งˆ
        if 'entities' in processed:
            entities = processed['entities']
            entity_count = sum(len(v) for v in entities.values() if isinstance(v, list))
            score += min(entity_count * 0.05, 0.2)
        
        return min(score, 1.0)
    
    def get_personalized_feed(self, user_id: str, limit: int = 10) -> List[Dict]:
        """๊ฐœ์ธํ™”๋œ ํ”ผ๋“œ ์ƒ์„ฑ"""
        user_prefs = self.user_preferences.get(user_id, {})
        preferred_categories = user_prefs.get('categories', [])
        
        # ์ฝ˜ํ…์ธ  ํ•„ํ„ฐ๋ง ๋ฐ ์ ์ˆ˜ ๊ณ„์‚ฐ
        scored_content = []
        for content in self.content_db:
            score = self._calculate_relevance_score(content, user_prefs)
            scored_content.append((score, content))
        
        # ์ ์ˆ˜์ˆœ ์ •๋ ฌ
        scored_content.sort(reverse=True, key=lambda x: x[0])
        
        # ์ƒ์œ„ N๊ฐœ ๋ฐ˜ํ™˜
        return [
            {
                'id': content['id'],
                'title': content['original']['title'],
                'summary': content['processed'].get('summary', {}).get('summary'),
                'category': content['processed'].get('classification', {}).get('primary_category'),
                'relevance_score': score,
                'quality_score': content['quality_score']
            }
            for score, content in scored_content[:limit]
        ]
    
    def _calculate_relevance_score(self, content: Dict, user_prefs: Dict) -> float:
        """์‚ฌ์šฉ์ž ๊ด€๋ จ์„ฑ ์ ์ˆ˜ ๊ณ„์‚ฐ"""
        score = content['quality_score']
        
        # ์นดํ…Œ๊ณ ๋ฆฌ ๋งค์นญ
        content_category = content['processed'].get('classification', {}).get('primary_category')
        preferred_categories = user_prefs.get('categories', [])
        
        if content_category in preferred_categories:
            score += 0.3
        
        # ์ตœ์‹ ์„ฑ ๋ณด๋„ˆ์Šค
        ingested_time = datetime.fromisoformat(content['ingested_at'])
        hours_old = (datetime.now() - ingested_time).total_seconds() / 3600
        
        if hours_old < 24:
            score += 0.2 * (1 - hours_old / 24)
        
        # ์ธ๊ธฐ๋„ ๋ณด๋„ˆ์Šค
        engagement = content.get('engagement_score', 0)
        score += min(engagement * 0.1, 0.2)
        
        return score
    
    async def generate_newsletter(self, user_id: str) -> str:
        """๊ฐœ์ธํ™”๋œ ๋‰ด์Šค๋ ˆํ„ฐ ์ƒ์„ฑ"""
        feed = self.get_personalized_feed(user_id, limit=5)
        
        if not feed:
            return "์˜ค๋Š˜์€ ์ƒˆ๋กœ์šด ์ฝ˜ํ…์ธ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
        
        # ๋‰ด์Šค๋ ˆํ„ฐ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
        content_summaries = '\n\n'.join([
            f"**{item['title']}**\n{item['summary']}\n์นดํ…Œ๊ณ ๋ฆฌ: {item['category']}"
            for item in feed
        ])
        
        prompt = f"""
        ๋‹ค์Œ ์ฝ˜ํ…์ธ ๋“ค์„ ๋ฐ”ํƒ•์œผ๋กœ ๋งค๋ ฅ์ ์ธ ๋‰ด์Šค๋ ˆํ„ฐ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.
        ์นœ๊ทผํ•˜๊ณ  ํฅ๋ฏธ๋กœ์šด ํ†ค์œผ๋กœ ์ž‘์„ฑํ•˜๋˜, ๊ฐ ์ฝ˜ํ…์ธ ์˜ ํ•ต์‹ฌ์„ ์ž˜ ์ „๋‹ฌํ•˜์„ธ์š”.
        
        ์ฝ˜ํ…์ธ :
        {content_summaries}
        
        ๋‰ด์Šค๋ ˆํ„ฐ ํ˜•์‹:
        - ์ธ์‚ฌ๋ง๋กœ ์‹œ์ž‘
        - ๊ฐ ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ„๋žตํžˆ ์†Œ๊ฐœ
        - ๋งˆ๋ฌด๋ฆฌ ์ธ์‚ฌ
        """
        
        newsletter = await self.agent.model.generate_content(prompt)
        return newsletter.text
    
    def get_trending_topics(self, time_window_hours: int = 24) -> List[Dict]:
        """ํŠธ๋ Œ๋”ฉ ํ† ํ”ฝ ๋ถ„์„"""
        cutoff_time = datetime.now() - timedelta(hours=time_window_hours)
        
        # ์ตœ๊ทผ ์ฝ˜ํ…์ธ  ํ•„ํ„ฐ๋ง
        recent_content = [
            c for c in self.content_db
            if datetime.fromisoformat(c['ingested_at']) > cutoff_time
        ]
        
        # ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ง‘๊ณ„
        category_counts = {}
        category_engagement = {}
        
        for content in recent_content:
            category = content['processed'].get('classification', {}).get('primary_category', 'Unknown')
            
            category_counts[category] = category_counts.get(category, 0) + 1
            category_engagement[category] = category_engagement.get(category, 0) + content.get('engagement_score', 0)
        
        # ํŠธ๋ Œ๋”ฉ ์ ์ˆ˜ ๊ณ„์‚ฐ
        trending = []
        for category in category_counts:
            score = (category_counts[category] * 0.6) + (category_engagement.get(category, 0) * 0.4)
            trending.append({
                'category': category,
                'count': category_counts[category],
                'engagement': category_engagement.get(category, 0),
                'trending_score': score
            })
        
        trending.sort(reverse=True, key=lambda x: x['trending_score'])
        return trending[:10]
    
    def _generate_id(self) -> str:
        """๊ณ ์œ  ID ์ƒ์„ฑ"""
        import uuid
        return str(uuid.uuid4())
๐Ÿš€ ํ”Œ๋žซํผ ํ™œ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค ์ด ํ๋ ˆ์ด์…˜ ํ”Œ๋žซํผ์€ ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด:

1. ๊ธฐ์—… ๋‚ด๋ถ€ ์ง€์‹ ๊ด€๋ฆฌ
ํšŒ์‚ฌ ๋‚ด ๋‹ค์–‘ํ•œ ๋ฌธ์„œ์™€ ๋ฆฌํฌํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ  ์š”์•ฝํ•ด์„œ ์ง์›๋“ค์—๊ฒŒ ์ œ๊ณต

2. ๋ฏธ๋””์–ด ๋ชจ๋‹ˆํ„ฐ๋ง
ํŠน์ • ํ‚ค์›Œ๋“œ๋‚˜ ์ฃผ์ œ์— ๋Œ€ํ•œ ๋‰ด์Šค๋ฅผ ์ž๋™์œผ๋กœ ์ˆ˜์ง‘ํ•˜๊ณ  ๋ถ„์„

3. ๊ฐœ์ธ ํ•™์Šต ๋„๊ตฌ
๊ด€์‹ฌ ๋ถ„์•ผ์˜ ์ตœ์‹  ์ •๋ณด๋ฅผ ์ž๋™์œผ๋กœ ํ๋ ˆ์ด์…˜ํ•ด์„œ ํ•™์Šต ์ž๋ฃŒ๋กœ ํ™œ์šฉ

4. ๋งˆ์ผ€ํŒ… ์ธ์‚ฌ์ดํŠธ
์—…๊ณ„ ํŠธ๋ Œ๋“œ์™€ ๊ฒฝ์Ÿ์‚ฌ ๋™ํ–ฅ์„ ์ž๋™์œผ๋กœ ๋ถ„์„ํ•˜๊ณ  ๋ฆฌํฌํŠธ ์ƒ์„ฑ

์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์ด๋Ÿฐ ํ๋ ˆ์ด์…˜ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋ฉด, ๋งŽ์€ ๊ธฐ์—…๊ณผ ๊ฐœ์ธ๋“ค์ด ๊ด€์‹ฌ์„ ๊ฐ€์งˆ ๊ฑฐ์•ผ!

๐Ÿ”ฎ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ: ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ฒ˜๋ฆฌ

Gemini API์˜ ์ง„์งœ ๊ฐ•์ ์€ ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ฒ˜๋ฆฌ์•ผ! ํ…์ŠคํŠธ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ด๋ฏธ์ง€, ๋™์˜์ƒ๊นŒ์ง€ ํ•จ๊ป˜ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐ŸŽฌ

์ด๋ฏธ์ง€์™€ ํ…์ŠคํŠธ ํ†ตํ•ฉ ๋ถ„์„

์ด๋ฏธ์ง€๊ฐ€ ํฌํ•จ๋œ ๋ฌธ์„œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž:

import PIL.Image
import io
import base64

class MultimodalAgent:
    """๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ฒ˜๋ฆฌ ์—์ด์ „ํŠธ"""
    
    def __init__(self):
        self.vision_model = genai.GenerativeModel('gemini-pro-vision')
        self.text_model = genai.GenerativeModel('gemini-pro')
        
    def analyze_image_with_text(self, image_path: str, text_context: str = None) -> Dict:
        """
        ์ด๋ฏธ์ง€์™€ ํ…์ŠคํŠธ๋ฅผ ํ•จ๊ป˜ ๋ถ„์„
        
        Args:
            image_path: ์ด๋ฏธ์ง€ ํŒŒ์ผ ๊ฒฝ๋กœ
            text_context: ์ถ”๊ฐ€ ํ…์ŠคํŠธ ์ปจํ…์ŠคํŠธ
        """
        # ์ด๋ฏธ์ง€ ๋กœ๋“œ
        image = PIL.Image.open(image_path)
        
        # ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
        if text_context:
            prompt = f"""
            ๋‹ค์Œ ์ด๋ฏธ์ง€๋ฅผ ๋ถ„์„ํ•˜๊ณ , ์ œ๊ณต๋œ ํ…์ŠคํŠธ ์ปจํ…์ŠคํŠธ์™€ ์—ฐ๊ด€์ง€์–ด ์„ค๋ช…ํ•˜์„ธ์š”.
            
            ํ…์ŠคํŠธ ์ปจํ…์ŠคํŠธ:
            {text_context}
            
            ๋‹ค์Œ ํ•ญ๋ชฉ์„ ํฌํ•จํ•˜์—ฌ ๋ถ„์„ํ•˜์„ธ์š”:
            1. ์ด๋ฏธ์ง€์— ๋ฌด์—‡์ด ๋ณด์ด๋Š”๊ฐ€?
            2. ํ…์ŠคํŠธ์™€ ์ด๋ฏธ์ง€์˜ ๊ด€๋ จ์„ฑ
            3. ์ฃผ์š” ์ธ์‚ฌ์ดํŠธ
            4. ์ถ”์ฒœ ์•ก์…˜
            """
        else:
            prompt = """
            ์ด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ธํžˆ ๋ถ„์„ํ•˜๊ณ  ๋‹ค์Œ์„ ์„ค๋ช…ํ•˜์„ธ์š”:
            1. ์ฃผ์š” ๊ฐ์ฒด์™€ ์žฅ๋ฉด
            2. ์ด๋ฏธ์ง€์˜ ๋ชฉ์ ์ด๋‚˜ ๋ฉ”์‹œ์ง€
            3. ์ฃผ๋ชฉํ•  ๋งŒํ•œ ์„ธ๋ถ€์‚ฌํ•ญ
            4. ๊ฐ€๋Šฅํ•œ ํ™œ์šฉ ๋ฐฉ์•ˆ
            """
        
        # ์ด๋ฏธ์ง€ ๋ถ„์„
        response = self.vision_model.generate_content([prompt, image])
        
        return {
            'analysis': response.text,
            'image_path': image_path,
            'has_context': text_context is not None
        }
    
    def extract_text_from_image(self, image_path: str) -> str:
        """์ด๋ฏธ์ง€์—์„œ ํ…์ŠคํŠธ ์ถ”์ถœ (OCR)"""
        image = PIL.Image.open(image_path)
        
        prompt = """
        ์ด ์ด๋ฏธ์ง€์— ์žˆ๋Š” ๋ชจ๋“  ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•˜์„ธ์š”.
        ํ…์ŠคํŠธ์˜ ์›๋ž˜ ํ˜•์‹๊ณผ ๊ตฌ์กฐ๋ฅผ ์ตœ๋Œ€ํ•œ ์œ ์ง€ํ•˜์„ธ์š”.
        """
        
        response = self.vision_model.generate_content([prompt, image])
        return response.text
    
    def analyze_document_with_images(self, 
                                    text: str, 
                                    image_paths: List[str]) -> Dict:
        """
        ํ…์ŠคํŠธ์™€ ์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€๋ฅผ ํฌํ•จํ•œ ๋ฌธ์„œ ์ข…ํ•ฉ ๋ถ„์„
        """
        results = {
            'text_analysis': {},
            'image_analyses': [],
            'integrated_summary': ''
        }
        
        # 1. ํ…์ŠคํŠธ ๋ถ„์„
        text_prompt = f"""
        ๋‹ค์Œ ๋ฌธ์„œ๋ฅผ ๋ถ„์„ํ•˜์„ธ์š”:
        
        {text}
        
        ํ•ต์‹ฌ ๋‚ด์šฉ์„ ์š”์•ฝํ•˜๊ณ  ์ฃผ์š” ํฌ์ธํŠธ๋ฅผ ์ถ”์ถœํ•˜์„ธ์š”.
        """
        
        text_response = self.text_model.generate_content(text_prompt)
        results['text_analysis'] = {
            'summary': text_response.text,
            'length': len(text)
        }
        
        # 2. ๊ฐ ์ด๋ฏธ์ง€ ๋ถ„์„
        for idx, image_path in enumerate(image_paths):
            image_analysis = self.analyze_image_with_text(image_path, text[:500])
            results['image_analyses'].append({
                'image_index': idx,
                'path': image_path,
                'analysis': image_analysis['analysis']
            })
        
        # 3. ํ†ตํ•ฉ ์š”์•ฝ ์ƒ์„ฑ
        integrated_prompt = f"""
        ๋‹ค์Œ์€ ๋ฌธ์„œ์˜ ํ…์ŠคํŠธ ๋ถ„์„๊ณผ ์ด๋ฏธ์ง€ ๋ถ„์„ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.
        ์ด๋ฅผ ํ†ตํ•ฉํ•˜์—ฌ ๋ฌธ์„œ ์ „์ฒด์— ๋Œ€ํ•œ ์ข…ํ•ฉ์ ์ธ ์š”์•ฝ์„ ์ž‘์„ฑํ•˜์„ธ์š”.
        
        ํ…์ŠคํŠธ ์š”์•ฝ:
        {results['text_analysis']['summary']}
        
        ์ด๋ฏธ์ง€ ๋ถ„์„๋“ค:
        {chr(10).join([f"์ด๋ฏธ์ง€ {i+1}: {img['analysis'][:200]}..." for i, img in enumerate(results['image_analyses'])])}
        
        ํ†ตํ•ฉ ์š”์•ฝ์„ ์ž‘์„ฑํ•˜์„ธ์š” (300์ž ์ด๋‚ด):
        """
        
        integrated_response = self.text_model.generate_content(integrated_prompt)
        results['integrated_summary'] = integrated_response.text
        
        return results
    
    def classify_image(self, image_path: str, categories: List[str]) -> Dict:
        """์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜"""
        image = PIL.Image.open(image_path)
        
        categories_str = ', '.join(categories)
        prompt = f"""
        ์ด ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์Œ ์นดํ…Œ๊ณ ๋ฆฌ ์ค‘ ํ•˜๋‚˜๋กœ ๋ถ„๋ฅ˜ํ•˜์„ธ์š”: {categories_str}
        
        JSON ํ˜•์‹์œผ๋กœ ์‘๋‹ต:
        {{
            "category": "์„ ํƒ๋œ ์นดํ…Œ๊ณ ๋ฆฌ",
            "confidence": 0.95,
            "reasoning": "๋ถ„๋ฅ˜ ๊ทผ๊ฑฐ"
        }}
        """
        
        response = self.vision_model.generate_content([prompt, image])
        
        try:
            json_start = response.text.find('{')
            json_end = response.text.rfind('}') + 1
            return json.loads(response.text[json_start:json_end])
        except:
            return {
                'category': 'Unknown',
                'confidence': 0.0,
                'reasoning': 'Parse error'
            }
์‹ค์ „ ์˜ˆ์ œ: ์†Œ์…œ ๋ฏธ๋””์–ด ์ฝ˜ํ…์ธ  ๋ถ„์„

์†Œ์…œ ๋ฏธ๋””์–ด ํฌ์ŠคํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๋ถ„์„ํ•˜๋Š” ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž:

class SocialMediaAnalyzer:
    """์†Œ์…œ ๋ฏธ๋””์–ด ์ฝ˜ํ…์ธ  ๋ถ„์„๊ธฐ"""
    
    def __init__(self):
        self.multimodal = MultimodalAgent()
        self.text_model = genai.GenerativeModel('gemini-pro')
        
    def analyze_post(self, post: Dict) -> Dict:
        """
        ์†Œ์…œ ๋ฏธ๋””์–ด ํฌ์ŠคํŠธ ์ข…ํ•ฉ ๋ถ„์„
        
        Args:
            post: {
                'text': str,
                'images': List[str],  # ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ๋“ค
                'platform': str,  # 'instagram', 'twitter', 'facebook' ๋“ฑ
                'author': str,
                'timestamp': str
            }
        """
        analysis = {
            'post_id': post.get('id', 'unknown'),
            'platform': post['platform'],
            'timestamp': post['timestamp']
        }
        
        # 1. ํ…์ŠคํŠธ ๊ฐ์ • ๋ถ„์„
        if post.get('text'):
            analysis['sentiment'] = self._analyze_sentiment(post['text'])
            analysis['topics'] = self._extract_topics(post['text'])
            analysis['hashtags'] = self._extract_hashtags(post['text'])
        
        # 2. ์ด๋ฏธ์ง€ ๋ถ„์„
        if post.get('images'):
            analysis['image_analysis'] = []
            for img_path in post['images']:
                img_result = self.multimodal.analyze_image_with_text(
                    img_path,
                    post.get('text', '')
                )
                analysis['image_analysis'].append(img_result)
        
        # 3. ์ฝ˜ํ…์ธ  ํ’ˆ์งˆ ํ‰๊ฐ€
        analysis['quality_metrics'] = self._evaluate_quality(post, analysis)
        
        # 4. ์ฐธ์—ฌ๋„ ์˜ˆ์ธก
        analysis['engagement_prediction'] = self._predict_engagement(analysis)
        
        # 5. ์ถ”์ฒœ ์‚ฌํ•ญ
        analysis['recommendations'] = self._generate_recommendations(analysis)
        
        return analysis
    
    def _analyze_sentiment(self, text: str) -> Dict:
        """๊ฐ์ • ๋ถ„์„"""
        prompt = f"""
        ๋‹ค์Œ ์†Œ์…œ ๋ฏธ๋””์–ด ํฌ์ŠคํŠธ์˜ ๊ฐ์ •์„ ๋ถ„์„ํ•˜์„ธ์š”:
        
        "{text}"
        
        JSON ํ˜•์‹์œผ๋กœ ์‘๋‹ต:
        {{
            "overall_sentiment": "positive/neutral/negative",
            "sentiment_score": -1.0์—์„œ 1.0 ์‚ฌ์ด,
            "emotions": ["๊ธฐ์จ", "ํฅ๋ถ„" ๋“ฑ ๊ฐ์ • ๋ฆฌ์ŠคํŠธ],
            "tone": "์บ์ฃผ์–ผ/์ „๋ฌธ์ /์œ ๋จธ๋Ÿฌ์Šค ๋“ฑ"
        }}
        """
        
        response = self.text_model.generate_content(prompt)
        try:
            json_start = response.text.find('{')
            json_end = response.text.rfind('}') + 1
            return json.loads(response.text[json_start:json_end])
        except:
            return {'overall_sentiment': 'neutral', 'sentiment_score': 0.0}
    
    def _extract_topics(self, text: str) -> List[str]:
        """์ฃผ์ œ ์ถ”์ถœ"""
        prompt = f"""
        ๋‹ค์Œ ํ…์ŠคํŠธ์—์„œ ์ฃผ์š” ์ฃผ์ œ/ํ† ํ”ฝ์„ 3-5๊ฐœ ์ถ”์ถœํ•˜์„ธ์š”:
        
        "{text}"
        
        ์ฃผ์ œ๋งŒ ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ๋‚˜์—ดํ•˜์„ธ์š”.
        """
        
        response = self.text_model.generate_content(prompt)
        topics = [t.strip() for t in response.text.split(',')]
        return topics[:5]
    
    def _extract_hashtags(self, text: str) -> List[str]:
        """ํ•ด์‹œํƒœ๊ทธ ์ถ”์ถœ"""
        import re
        hashtags = re.findall(r'#\w+', text)
        return hashtags
    
    def _evaluate_quality(self, post: Dict, analysis: Dict) -> Dict:
        """์ฝ˜ํ…์ธ  ํ’ˆ์งˆ ํ‰๊ฐ€"""
        quality = {
            'text_quality': 0.5,
            'visual_quality': 0.5,
            'engagement_potential': 0.5,
            'overall_score': 0.5
        }
        
        # ํ…์ŠคํŠธ ํ’ˆ์งˆ
        text = post.get('text', '')
        if text:
            # ๊ธธ์ด ์ ์ ˆ์„ฑ
            if 50 < len(text) < 300:
                quality['text_quality'] += 0.2
            
            # ํ•ด์‹œํƒœ๊ทธ ์‚ฌ์šฉ
            if analysis.get('hashtags'):
                quality['text_quality'] += 0.1
            
            # ๊ฐ์ • ๋ช…ํ™•์„ฑ
            sentiment = analysis.get('sentiment', {})
            if abs(sentiment.get('sentiment_score', 0)) > 0.5:
                quality['text_quality'] += 0.2
        
        # ๋น„์ฃผ์–ผ ํ’ˆ์งˆ
        if post.get('images'):
            quality['visual_quality'] += 0.3
            if len(post['images']) > 1:
                quality['visual_quality'] += 0.2
        
        # ์ „์ฒด ์ ์ˆ˜
        quality['overall_score'] = (
            quality['text_quality'] * 0.4 +
            quality['visual_quality'] * 0.3 +
            quality['engagement_potential'] * 0.3
        )
        
        return quality
    
    def _predict_engagement(self, analysis: Dict) -> Dict:
        """์ฐธ์—ฌ๋„ ์˜ˆ์ธก"""
        # ๊ฐ„๋‹จํ•œ ํœด๋ฆฌ์Šคํ‹ฑ ๊ธฐ๋ฐ˜ ์˜ˆ์ธก
        score = 0.5
        
        # ๊ฐ์ •์ด ๊ฐ•ํ• ์ˆ˜๋ก ์ฐธ์—ฌ๋„ ๋†’์Œ
        sentiment = analysis.get('sentiment', {})
        sentiment_score = abs(sentiment.get('sentiment_score', 0))
        score += sentiment_score * 0.2
        
        # ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์œผ๋ฉด ์ฐธ์—ฌ๋„ ๋†’์Œ
        if analysis.get('image_analysis'):
            score += 0.2
        
        # ํ•ด์‹œํƒœ๊ทธ๊ฐ€ ์ ์ ˆํ•˜๋ฉด ์ฐธ์—ฌ๋„ ๋†’์Œ
        hashtag_count = len(analysis.get('hashtags', []))
        if 2 <= hashtag_count <= 5:
            score += 0.1
        
        return {
            'predicted_score': min(score, 1.0),
            'confidence': 0.7,
            'factors': {
                'sentiment_impact': sentiment_score * 0.2,
                'visual_impact': 0.2 if analysis.get('image_analysis') else 0,
                'hashtag_impact': 0.1 if 2 <= hashtag_count <= 5 else 0
            }
        }
    
    def _generate_recommendations(self, analysis: Dict) -> List[str]:
        """๊ฐœ์„  ์ถ”์ฒœ์‚ฌํ•ญ ์ƒ์„ฑ"""
        recommendations = []
        
        # ํ…์ŠคํŠธ ๊ด€๋ จ
        if not analysis.get('hashtags'):
            recommendations.append("๊ด€๋ จ ํ•ด์‹œํƒœ๊ทธ๋ฅผ 2-3๊ฐœ ์ถ”๊ฐ€ํ•˜์„ธ์š”")
        
        sentiment = analysis.get('sentiment', {})
        if abs(sentiment.get('sentiment_score', 0)) < 0.3:
            recommendations.append("๋” ๋ช…ํ™•ํ•œ ๊ฐ์ •์ด๋‚˜ ์˜๊ฒฌ์„ ํ‘œํ˜„ํ•˜์„ธ์š”")
        
        # ์ด๋ฏธ์ง€ ๊ด€๋ จ
        if not analysis.get('image_analysis'):
            recommendations.append("์‹œ๊ฐ์  ์š”์†Œ(์ด๋ฏธ์ง€/๋™์˜์ƒ)๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”")
        
        # ํ’ˆ์งˆ ๊ด€๋ จ
        quality = analysis.get('quality_metrics', {})
        if quality.get('overall_score', 0) < 0.6:
            recommendations.append("์ฝ˜ํ…์ธ ์˜ ์ „๋ฐ˜์ ์ธ ํ’ˆ์งˆ์„ ๊ฐœ์„ ํ•˜์„ธ์š”")
        
        return recommendations
๐Ÿ’ก ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ํ™œ์šฉ ํŒ ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ฒ˜๋ฆฌ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ํ™œ์šฉํ•˜๋ ค๋ฉด:

1. ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ
์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ ์ ˆํžˆ ์กฐ์ •ํ•˜๊ณ  ํ’ˆ์งˆ์„ ์ตœ์ ํ™”ํ•ด์„œ API ํ˜ธ์ถœ ๋น„์šฉ์„ ์ค„์—ฌ.

2. ์ปจํ…์ŠคํŠธ ์ œ๊ณต
์ด๋ฏธ์ง€๋งŒ ๋ณด๋‚ด๋Š” ๊ฒƒ๋ณด๋‹ค ๊ด€๋ จ ํ…์ŠคํŠธ ์ปจํ…์ŠคํŠธ๋ฅผ ํ•จ๊ป˜ ์ œ๊ณตํ•˜๋ฉด ๋” ์ •ํ™•ํ•œ ๋ถ„์„์„ ์–ป์„ ์ˆ˜ ์žˆ์–ด.

3. ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ
์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ๋Š” ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ํ™œ์šฉํ•ด์„œ ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•ด.

4. ๊ฒฐ๊ณผ ๊ฒ€์ฆ
AI์˜ ์ด๋ฏธ์ง€ ๋ถ„์„ ๊ฒฐ๊ณผ๋Š” ํ•ญ์ƒ ๊ฒ€์ฆ์ด ํ•„์š”ํ•ด. ํŠนํžˆ ์ค‘์š”ํ•œ ์˜์‚ฌ๊ฒฐ์ •์— ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋”์šฑ ๊ทธ๋ž˜.

๐Ÿ“Š ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๊ฐœ์„ 

์—์ด์ „ํŠธ๋ฅผ ๋ฐฐํฌํ•œ ํ›„์—๋Š” ์ง€์†์ ์ธ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๊ฐœ์„ ์ด ํ•„์š”ํ•ด. ์‹ค์ œ ์‚ฌ์šฉ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•ด์„œ ์‹œ์Šคํ…œ์„ ๊ณ„์† ๋ฐœ์ „์‹œ์ผœ์•ผ ํ•˜๊ฑฐ๋“ ! ๐Ÿ“ˆ

์„ฑ๋Šฅ ๋ฉ”ํŠธ๋ฆญ ์ถ”์ 

์‹œ์Šคํ…œ์˜ ์„ฑ๋Šฅ์„ ์ถ”์ ํ•˜๋Š” ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž:

class PerformanceMonitor:
    """์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ"""
    
    def __init__(self):
        self.metrics = {
            'api_calls': [],
            'response_times': [],
            'error_rates': [],
            'user_satisfaction': []
        }
        
    def log_api_call(self, 
                    operation: str,
                    response_time: float,
                    success: bool,
                    token_usage: Dict):
        """API ํ˜ธ์ถœ ๋กœ๊น…"""
        self.metrics['api_calls'].append({
            'timestamp': datetime.now(),
            'operation': operation,
            'response_time': response_time,
            'success': success,
            'token_usage': token_usage
        })
        
        self.metrics['response_times'].append(response_time)
        
        if not success:
            self.metrics['error_rates'].append({
                'timestamp': datetime.now(),
                'operation': operation
            })
    
    def get_performance_summary(self, time_window_hours: int = 24) -> Dict:
        """์„ฑ๋Šฅ ์š”์•ฝ ํ†ต๊ณ„"""
        cutoff = datetime.now() - timedelta(hours=time_window_hours)
        
        recent_calls = [
            call for call in self.metrics['api_calls']
            if call['timestamp'] > cutoff
        ]
        
        if not recent_calls:
            return {}
        
        total_calls = len(recent_calls)
        successful_calls = sum(1 for call in recent_calls if call['success'])
        failed_calls = total_calls - successful_calls
        
        response_times = [call['response_time'] for call in recent_calls]
        avg_response_time = sum(response_times) / len(response_times)
        
        return {
            'time_window': f'{time_window_hours}์‹œ๊ฐ„',
            'total_calls': total_calls,
            'successful_calls': successful_calls,
            'failed_calls': failed_calls,
            'success_rate': f'{(successful_calls/total_calls*100):.2f}%',
            'average_response_time': f'{avg_response_time:.2f}์ดˆ',
            'min_response_time': f'{min(response_times):.2f}์ดˆ',
            'max_response_time': f'{max(response_times):.2f}์ดˆ'
        }
    
    def get_operation_breakdown(self) -> Dict:
        """์ž‘์—…๋ณ„ ํ†ต๊ณ„"""
        operation_stats = {}
        
        for call in self.metrics['api_calls']:
            op = call['operation']
            if op not in operation_stats:
                operation_stats[op] = {
                    'count': 0,
                    'total_time': 0,
                    'successes': 0,
                    'failures': 0
                }
            
            operation_stats[op]['count'] += 1
            operation_stats[op]['total_time'] += call['response_time']
            
            if call['success']:
                operation_stats[op]['successes'] += 1
            else:
                operation_stats[op]['failures'] += 1
        
        # ํ‰๊ท  ๊ณ„์‚ฐ
        for op in operation_stats:
            stats = operation_stats[op]
            stats['avg_time'] = stats['total_time'] / stats['count']
            stats['success_rate'] = (stats['successes'] / stats['count'] * 100)
        
        return operation_stats
    
    def detect_anomalies(self) -> List[Dict]:
        """์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€"""
        anomalies = []
        
        # ์ตœ๊ทผ 1์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ
        recent_hour = datetime.now() - timedelta(hours=1)
        recent_calls = [
            call for call in self.metrics['api_calls']
            if call['timestamp'] > recent_hour
        ]
        
        if not recent_calls:
            return anomalies
        
        # ์‘๋‹ต ์‹œ๊ฐ„ ์ด์ƒ
        response_times = [call['response_time'] for call in recent_calls]
        avg_time = sum(response_times) / len(response_times)
        
        for call in recent_calls[-10:]:  # ์ตœ๊ทผ 10๊ฐœ ์ฒดํฌ
            if call['response_time'] > avg_time * 2:
                anomalies.append({
                    'type': 'slow_response',
                    'timestamp': call['timestamp'],
                    'operation': call['operation'],
                    'response_time': call['response_time'],
                    'threshold': avg_time * 2
                })
        
        # ์—๋Ÿฌ์œจ ์ด์ƒ
        recent_errors = [call for call in recent_calls if not call['success']]
        error_rate = len(recent_errors) / len(recent_calls)
        
        if error_rate > 0.1:  # 10% ์ด์ƒ
            anomalies.append({
                'type': 'high_error_rate',
                'timestamp': datetime.now(),
                'error_rate': f'{error_rate*100:.2f}%',
                'threshold': '10%'
            })
        
        return anomalies
    
    def generate_report(self) -> str:
        """์ข…ํ•ฉ ๋ฆฌํฌํŠธ ์ƒ์„ฑ"""
        summary = self.get_performance_summary(24)
        operations = self.get_operation_breakdown()
        anomalies = self.detect_anomalies()
        
        report = f"""
=== ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฆฌํฌํŠธ ===
์ƒ์„ฑ ์‹œ๊ฐ„: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}

[์ „์ฒด ์š”์•ฝ (24์‹œ๊ฐ„)]
- ์ด API ํ˜ธ์ถœ: {summary.get('total_calls', 0)}ํšŒ
- ์„ฑ๊ณต๋ฅ : {summary.get('success_rate', 'N/A')}
- ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: {summary.get('average_response_time', 'N/A')}

[์ž‘์—…๋ณ„ ํ†ต๊ณ„]
"""
        
        for op, stats in operations.items():
            report += f"\n{op}:"
            report += f"\n  - ํ˜ธ์ถœ ํšŸ์ˆ˜: {stats['count']}ํšŒ"
            report += f"\n  - ํ‰๊ท  ์‹œ๊ฐ„: {stats['avg_time']:.2f}์ดˆ"
            report += f"\n  - ์„ฑ๊ณต๋ฅ : {stats['success_rate']:.2f}%"
        
        if anomalies:
            report += "\n\n[โš ๏ธ ๊ฐ์ง€๋œ ์ด์ƒ ์ง•ํ›„]"
            for anomaly in anomalies:
                report += f"\n- {anomaly['type']}: {anomaly}"
        else:
            report += "\n\n[โœ… ์ด์ƒ ์ง•ํ›„ ์—†์Œ]"
        
        return report
์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ ์ˆ˜์ง‘

์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ์„ ์ˆ˜์ง‘ํ•˜๊ณ  ๋ถ„์„ํ•˜๋Š” ์‹œ์Šคํ…œ๋„ ์ค‘์š”ํ•ด:

class FeedbackCollector:
    """์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ ์ˆ˜์ง‘ ๋ฐ ๋ถ„์„"""
    
    def __init__(self):
        self.feedback_db = []
        self.text_model = genai.GenerativeModel('gemini-pro')
        
    def collect_feedback(self, 
                        user_id: str,
                        operation: str,
                        result_id: str,
                        rating: int,
                        comment: str = None):
        """ํ”ผ๋“œ๋ฐฑ ์ˆ˜์ง‘"""
        feedback = {
            'id': self._generate_id(),
            'user_id': user_id,
            'operation': operation,
            'result_id': result_id,
            'rating': rating,  # 1-5
            'comment': comment,
            'timestamp': datetime.now().isoformat()
        }
        
        self.feedback_db.append(feedback)
        
        # ๋ถ€์ •์  ํ”ผ๋“œ๋ฐฑ ์ฆ‰์‹œ ์•Œ๋ฆผ
        if rating <= 2:
            self._alert_negative_feedback(feedback)
    
    def analyze_feedback_trends(self) -> Dict:
        """ํ”ผ๋“œ๋ฐฑ ํŠธ๋ Œ๋“œ ๋ถ„์„"""
        if not self.feedback_db:
            return {}
        
        # ํ‰๊ท  ํ‰์ 
        avg_rating = sum(f['rating'] for f in self.feedback_db) / len(self.feedback_db)
        
        # ์ž‘์—…๋ณ„ ํ‰์ 
        operation_ratings = {}
        for feedback in self.feedback_db:
            op = feedback['operation']
            if op not in operation_ratings:
                operation_ratings[op] = []
            operation_ratings[op].append(feedback['rating'])
        
        operation_avg = {
            op: sum(ratings) / len(ratings)
            for op, ratings in operation_ratings.items()
        }
        
        # ์ตœ๊ทผ ํŠธ๋ Œ๋“œ (์ตœ๊ทผ 30์ผ)
        recent_cutoff = datetime.now() - timedelta(days=30)
        recent_feedback = [
            f for f in self.feedback_db
            if datetime.fromisoformat(f['timestamp']) > recent_cutoff
        ]
        
        recent_avg = sum(f['rating'] for f in recent_feedback) / len(recent_feedback) if recent_feedback else 0
        
        return {
            'overall_average': f'{avg_rating:.2f}',
            'recent_average': f'{recent_avg:.2f}',
            'total_feedback': len(self.feedback_db),
            'operation_breakdown': operation_avg,
            'trend': 'improving' if recent_avg > avg_rating else 'declining'
        }
    
    def extract_improvement_suggestions(self) -> List[str]:
        """๊ฐœ์„  ์ œ์•ˆ ์ถ”์ถœ"""
        # ๋ถ€์ •์  ํ”ผ๋“œ๋ฐฑ์˜ ์ฝ”๋ฉ˜ํŠธ ์ˆ˜์ง‘
        negative_feedback = [
            f for f in self.feedback_db
            if f['rating'] <= 2 and f.get('comment')
        ]
        
        if not negative_feedback:
            return []
        
        comments = '\n'.join([f['comment'] for f in negative_feedback])
        
        prompt = f"""
        ๋‹ค์Œ์€ ์‚ฌ์šฉ์ž๋“ค์˜ ๋ถ€์ •์  ํ”ผ๋“œ๋ฐฑ ์ฝ”๋ฉ˜ํŠธ๋“ค์ž…๋‹ˆ๋‹ค.
        ์ด๋ฅผ ๋ถ„์„ํ•˜์—ฌ ์‹œ์Šคํ…œ ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ์‚ฌํ•ญ์„ 3-5๊ฐœ ์ถ”์ถœํ•˜์„ธ์š”.
        
        ํ”ผ๋“œ๋ฐฑ:
        {comments}
        
        ๊ฐœ์„  ์ œ์•ˆ์‚ฌํ•ญ์„ ๋ฒˆํ˜ธ๋ฅผ ๋งค๊ฒจ ๋‚˜์—ดํ•˜์„ธ์š”.
        """
        
        response = self.text_model.generate_content(prompt)
        suggestions = [
            line.strip() 
            for line in response.text.split('\n') 
            if line.strip() and any(c.isdigit() for c in line[:3])
        ]
        
        return suggestions[:5]
    
    def _alert_negative_feedback(self, feedback: Dict):
        """๋ถ€์ •์  ํ”ผ๋“œ๋ฐฑ ์•Œ๋ฆผ"""
        print(f"""
        โš ๏ธ ๋ถ€์ •์  ํ”ผ๋“œ๋ฐฑ ๊ฐ์ง€!
        ์‚ฌ์šฉ์ž: {feedback['user_id']}
        ์ž‘์—…: {feedback['operation']}
        ํ‰์ : {feedback['rating']}/5
        ์ฝ”๋ฉ˜ํŠธ: {feedback.get('comment', 'N/A')}
        """)
    
    def _generate_id(self) -> str:
        import uuid
        return str(uuid.uuid4())
๐Ÿ“Š ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ ๊ฐœ์„  ์‚ฌ์ดํด ์„ฑ๊ณต์ ์ธ AI ์—์ด์ „ํŠธ ์šด์˜์„ ์œ„ํ•œ ๊ฐœ์„  ์‚ฌ์ดํด:

1. ์ธก์ • (Measure)
์„ฑ๋Šฅ ๋ฉ”ํŠธ๋ฆญ, ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ, ๋น„์šฉ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘

2. ๋ถ„์„ (Analyze)
ํŠธ๋ Œ๋“œ ํŒŒ์•…, ๋ฌธ์ œ์  ์‹๋ณ„, ๊ฐœ์„  ๊ธฐํšŒ ๋ฐœ๊ฒฌ

3. ๊ฐœ์„  (Improve)
ํ”„๋กฌํ”„ํŠธ ์ตœ์ ํ™”, ๋กœ์ง ๊ฐœ์„ , ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€

4. ๊ฒ€์ฆ (Validate)
A/B ํ…Œ์ŠคํŠธ, ์„ฑ๋Šฅ ๋น„๊ต, ์‚ฌ์šฉ์ž ๋งŒ์กฑ๋„ ํ™•์ธ

์ด ์‚ฌ์ดํด์„ 2-4์ฃผ๋งˆ๋‹ค ๋ฐ˜๋ณตํ•˜๋ฉด์„œ ์ง€์†์ ์œผ๋กœ ์‹œ์Šคํ…œ์„ ๊ฐœ์„ ํ•ด ๋‚˜๊ฐ€๋Š” ๊ฒŒ ์ค‘์š”ํ•ด!

๐ŸŽ“ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์ง€? ๐Ÿ˜Š ์šฐ๋ฆฌ๋Š” Gemini API๋ฅผ ํ™œ์šฉํ•ด์„œ ์ž๋™์š”์•ฝ๊ณผ ์ž๋™๋ถ„๋ฅ˜ ์—์ด์ „ํŠธ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ์‚ดํŽด๋ดค์–ด.

ํ•ต์‹ฌ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜๋ฉด:

โœ… ๊ธฐ๋ณธ ์„ค์ •
โ€ข API ํ‚ค ๋ฐœ๊ธ‰ ๋ฐ ๋ณด์•ˆ ๊ด€๋ฆฌ
โ€ข ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ตฌ์ถ•
โ€ข ๊ธฐ๋ณธ ๋ชจ๋ธ ์ดˆ๊ธฐํ™”

โœ… ์ž๋™์š”์•ฝ
โ€ข ๊ธฐ๋ณธ ์š”์•ฝ๋ถ€ํ„ฐ ๊ณ ๊ธ‰ ๋ฉ€ํ‹ฐํŒจ์Šค ์š”์•ฝ๊นŒ์ง€
โ€ข ๋‹ค์–‘ํ•œ ์Šคํƒ€์ผ๊ณผ ๊ธธ์ด ์ง€์›
โ€ข ๊ธด ๋ฌธ์„œ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์ฒญํฌ ๋ถ„ํ• 

โœ… ์ž๋™๋ถ„๋ฅ˜
โ€ข ๋‹จ์ผ/๋‹ค์ค‘ ๋ ˆ์ด๋ธ” ๋ถ„๋ฅ˜
โ€ข ๊ณ„์ธต์  ๋ถ„๋ฅ˜ ์‹œ์Šคํ…œ
โ€ข ์‹ ๋ขฐ๋„ ๊ธฐ๋ฐ˜ ํ•„ํ„ฐ๋ง

โœ… ํ†ตํ•ฉ ์—์ด์ „ํŠธ
โ€ข ์š”์•ฝ๊ณผ ๋ถ„๋ฅ˜์˜ ๊ฒฐํ•ฉ
โ€ข ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ฒ˜๋ฆฌ (ํ…์ŠคํŠธ + ์ด๋ฏธ์ง€)
โ€ข ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์˜ˆ์ œ

โœ… ์ตœ์ ํ™” ๋ฐ ์šด์˜
โ€ข ์„ฑ๋Šฅ ์ตœ์ ํ™”์™€ ๋น„์šฉ ๊ด€๋ฆฌ
โ€ข ์—๋Ÿฌ ํ•ธ๋“ค๋ง๊ณผ ์•ˆ์ •์„ฑ
โ€ข ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์ง€์†์  ๊ฐœ์„ 

์ด์ œ ๋‹น์‹ ๋„ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” AI ์—์ด์ „ํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด! ๐Ÿš€

์‹œ์ž‘์€ ์ž‘๊ฒŒ ํ•ด๋„ ๊ดœ์ฐฎ์•„. ๊ฐ„๋‹จํ•œ ์š”์•ฝ ๊ธฐ๋Šฅ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ, ์ ์ฐจ ๋ถ„๋ฅ˜, ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ฒ˜๋ฆฌ ๋“ฑ์„ ์ถ”๊ฐ€ํ•ด ๋‚˜๊ฐ€๋ฉด ๋ผ. ์ค‘์š”ํ•œ ๊ฑด ์‹ค์ œ๋กœ ๋งŒ๋“ค์–ด๋ณด๊ณ , ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์•„์„œ ๊ณ„์† ๊ฐœ์„ ํ•ด ๋‚˜๊ฐ€๋Š” ๊ฒƒ์ด์•ผ.

ํ˜น์‹œ ์ด๋Ÿฐ AI ์ž๋™ํ™” ์‹œ์Šคํ…œ์„ ์ง์ ‘ ๊ตฌ์ถ•ํ•˜๊ณ  ์‹ถ์€๋ฐ ๋„์›€์ด ํ•„์š”ํ•˜๋‹ค๋ฉด? ์žฌ๋Šฅ๋„ท์—์„œ ์ „๋ฌธ๊ฐ€๋“ค์˜ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜๋„ ์žˆ์–ด. ๋˜๋Š” ๋ฐ˜๋Œ€๋กœ, ๋‹น์‹ ์ด ์ด ๋ถ„์•ผ์˜ ์ „๋ฌธ๊ฐ€๋ผ๋ฉด ์žฌ๋Šฅ๋„ท์—์„œ ๋‹น์‹ ์˜ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•  ์ˆ˜๋„ ์žˆ๊ณ ! ๐Ÿ˜‰

AI ๊ธฐ์ˆ ์€ ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด. Gemini API๋„ ๊ณ„์† ์—…๋ฐ์ดํŠธ๋˜๋ฉด์„œ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๋“ค์ด ์ถ”๊ฐ€๋˜๊ณ  ์žˆ์ง€. ๊ทธ๋Ÿฌ๋‹ˆ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ํ™•์ธํ•˜๊ณ , ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋“ค๊ณผ ์ •๋ณด๋ฅผ ๊ตํ™˜ํ•˜๋Š” ๊ฒƒ๋„ ์žŠ์ง€ ๋งˆ!

์ž, ์ด์ œ ๋‹น์‹  ์ฐจ๋ก€์•ผ! ์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ฉ‹์ง„ AI ์—์ด์ „ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž. ํ™”์ดํŒ…! ๐Ÿ’ชโœจ

๐ŸŽ‰ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰ Gemini API ๋งˆ์Šคํ„ฐ ๊ณผ์ • ์™„๋ฃŒ ๐Ÿ“ ์ž๋™์š”์•ฝ ๐Ÿ—‚๏ธ ์ž๋™๋ถ„๋ฅ˜ ๐Ÿค– AI ์—์ด์ „ํŠธ
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.