반응형

앞선 글에서 로컬 LLM을 이용하여 에이전틱 프로그래밍 데모 해보기 를 진행해보았다.

하지만 문제가 있었는데, 테스트로 xeyes 를 만들어보라고 했을 때, llama3.1:8b 모델은 경량이라 그런지 하는 시늉만 하는 느낌이었다.

그래서 Google Gemini 를 연동하여 해보고자 한다.

 

문제점 발견 : llama 모델의 한계

llama 모델이 시늉만 한다는 내역을 아래와 같이 확인할 수 있다.

아래의 코드로 에이전트를 통해서 xeyes 프로그램을 생성하도록 실행시켜보았다.

더보기
import os
from crewai import Agent, Task, Crew, LLM

# 1. 로컬 Ollama 환경 설정
# Ollama는 기본적으로 11434 포트에서 OpenAI와 호환되는 로컬 API 서버를 제공합니다.
os.environ["OPENAI_API_KEY"] = "ollama" # 로컬 환경이므로 임의의 문자열을 넣으면 됩니다.

# 가지고 계신 8B 모델의 정확한 이름으로 변경하세요 (예: 'llama3', 'gemma:7b', 'phi3' 등)
target_model = "llama3.1:8b" 

llm = LLM(
    model=target_model,
    base_url="http://localhost:11434",
    temperature=0.1
)

# 2. 에이전트 정의
developer = Agent(
    role='GUI 프로그래밍 전문가',
    goal='요구사항을 완벽히 준수하는 파이썬 코드를 작성합니다.',
    backstory='당신은 특히 tkinter의 Canvas 위젯 작업 시 "이전 프레임 삭제(delete)"와 "정확한 좌표 계산"을 가장 중요하게 생각합니다.',
    llm=llm
)

reviewer = Agent(
    role='코드 품질 및 로직 검수관',
    goal='작성된 코드에서 불필요한 기능(코 등)이 포함되었는지, 화면 초기화 로직이 있는지 검사합니다.',
    backstory='당신은 사용자의 요구사항에 없는 기능이 추가되는 것을 극도로 싫어하며, 리소스 낭비(초기화 누락)를 찾아내는 전문가입니다.',
    llm=llm
)

# 3. 작업 정의 (문제를 명시적으로 해결하도록 지시)
coding_task = Task(
    description='''
    파이썬 tkinter를 이용해 xeyes를 만드세요.
    [필수 해결 과제]
    1. 화면 갱신 시 반드시 canvas.delete("all")을 호출하여 잔상이 남지 않게 하세요.
    2. '두 눈(흰자)'과 '두 눈동자(검은색)'만 그리세요. 코(Nose)나 입 등 다른 얼굴 부위는 절대로 그리지 마세요.
    3. 눈동자는 반드시 눈(흰자)의 영역 안에서만 움직여야 합니다. (삼각함수 사용)
    ''',
    expected_output='버그가 수정된 파이썬 소스 코드',
    agent=developer
)

review_task = Task(
    description='''
    전달받은 코드를 검토하세요. 
    - canvas.delete("all") 혹은 유사한 초기화 코드가 없다면 다시 작성하도록 지시하세요.
    - 코(Nose)를 그리는 코드가 포함되어 있다면 즉시 삭제하세요.
    - 모든 검토가 끝나면 최종 코드를 'fixed_xeyes.py'로 저장하세요.
    ''',
    expected_output='최종 검수 완료된 파이썬 코드',
    output_file='fixed_xeyes.py',
    agent=reviewer,
    context=[coding_task]
)

# 4. 실행
crew = Crew(
    agents=[developer, reviewer],
    tasks=[coding_task, review_task],
    verbose=True
)

crew.kickoff()

 

llama3.1:8b의 결과물

마우스 움직임에 눈이 따라다니지 않는다..

심지어 생성 결과에 따라서는 실행 조차 되지 않는 경우가 있었다.

생성된 코드는 아래 접은 글을 펼쳐서 확인 할 수 있다.

더보기
# ```python
import tkinter as tk
import math

class Xeyes:
    def __init__(self):
        self.root = tk.Tk()
        self.canvas = tk.Canvas(self.root, width=400, height=400)
        self.canvas.pack()

        self.eye_radius = 50
        self.pupil_radius = 10
        self.eye_x = 200
        self.eye_y = 150

    def update(self):
        self.canvas.delete("all") # 초기화 코드 추가
        self.canvas.create_oval(self.eye_x - self.eye_radius, self.eye_y - self.eye_radius,
                                self.eye_x + self.eye_radius, self.eye_y + self.eye_radius)
        self.canvas.create_oval(self.eye_x - self.pupil_radius, self.eye_y - self.pupil_radius,
                                self.eye_x + self.pupil_radius, self.eye_y + self.pupil_radius)

    def move_pupil(self):
        angle = 0
        x = self.eye_x + (self.eye_radius - self.pupil_radius) * math.cos(angle)
        y = self.eye_y + (self.eye_radius - self.pupil_radius) * math.sin(angle)
        self.canvas.create_oval(x - self.pupil_radius, y - self.pupil_radius,
                                x + self.pupil_radius, y + self.pupil_radius)

    def run(self):
        self.root.after(1000 // 60, self.update)
        self.move_pupil()
        self.root.mainloop()

xeyes = Xeyes()
xeyes.run()
# ```

Gemini 연동하기

Gemini 연동에 필요한 구글 SDK 패키지를 추가로 설치해야한다.

아래의 명령어로 설치하도록 하자.

pip install langchain-google-genai

 

Gemini API Key 획득하기

  1. Google AI Studio 접속: Google AI Studio (aistudio.google.com)에 Google 계정으로 로그인합니다.
  2. API 키 생성: 좌측 메뉴에서 **'Get API key'**를 클릭한 후, 'Create API key' 버튼을 누릅니다.
  3. 프로젝트 선택: 새로운 Google Cloud 프로젝트를 생성하거나 기존 프로젝트를 선택하여 키를 발급받습니다.
  4. 키 복사: 생성된 AIzaSy... 로 시작하는 문자열을 복사해 둡니다. 이 키는 절대 외부에 노출되지 않도록 주의해야 합니다.

 

 

Gemini 를 연동하기 위한 코드 수정하기

앞선 코드의 상단을 아래와 같이 바꾼다.

# 1. Google Gemini 환경 설정
# 발급받은 구글 API 키를 환경 변수에 등록합니다.
os.environ["GEMINI_API_KEY"] = "AIza*****"

# CrewAI 내부의 LiteLLM 라우터를 통해 Gemini를 호출합니다.
# 모델 이름 앞에 'gemini/'를 붙여주는 것이 핵심입니다.
llm = LLM(
    # model="gemini/gemini-2.5-pro", # 가장 똑똑하고 추론 능력이 뛰어난 Pro 모델 (복잡한 코딩, 아키텍처 설계용)
    model="gemini/gemini-2.5-flash", # 빠르고 가성비가 좋은 Flash 모델 (가벼운 스크립트, 데모 테스트용)
    temperature=0.1
)

 

전체 코드는 아래의 접은 글을 펼쳐서 확인할 수 있다.

더보기
import os
from crewai import Agent, Task, Crew, LLM

# 1. Google Gemini 환경 설정
# 발급받은 구글 API 키를 환경 변수에 등록합니다.
os.environ["GEMINI_API_KEY"] = "AIza****"

# CrewAI 내부의 LiteLLM 라우터를 통해 Gemini를 호출합니다.
# 모델 이름 앞에 'gemini/'를 붙여주는 것이 핵심입니다.
llm = LLM(
    # model="gemini/gemini-2.5-pro", # 가장 똑똑하고 추론 능력이 뛰어난 Pro 모델 (복잡한 코딩, 아키텍처 설계용)
    model="gemini/gemini-2.5-flash", # 빠르고 가성비가 좋은 Flash 모델 (가벼운 스크립트, 데모 테스트용)
    temperature=0.1
)

# 2. 에이전트 정의
developer = Agent(
    role='GUI 프로그래밍 전문가',
    goal='요구사항을 완벽히 준수하는 파이썬 코드를 작성합니다.',
    backstory='당신은 특히 tkinter의 Canvas 위젯 작업 시 "이전 프레임 삭제(delete)"와 "정확한 좌표 계산"을 가장 중요하게 생각합니다.',
    llm=llm
)

reviewer = Agent(
    role='코드 품질 및 로직 검수관',
    goal='작성된 코드에서 불필요한 기능(코 등)이 포함되었는지, 화면 초기화 로직이 있는지 검사합니다.',
    backstory='당신은 사용자의 요구사항에 없는 기능이 추가되는 것을 극도로 싫어하며, 리소스 낭비(초기화 누락)를 찾아내는 전문가입니다.',
    llm=llm
)

# 3. 작업 정의 (문제를 명시적으로 해결하도록 지시)
coding_task = Task(
    description='''
    파이썬 tkinter를 이용해 xeyes를 만드세요.
    [필수 해결 과제]
    1. 화면 갱신 시 반드시 canvas.delete("all")을 호출하여 잔상이 남지 않게 하세요.
    2. '두 눈(흰자)'과 '두 눈동자(검은색)'만 그리세요. 코(Nose)나 입 등 다른 얼굴 부위는 절대로 그리지 마세요.
    3. 눈동자는 반드시 눈(흰자)의 영역 안에서만 움직여야 합니다. (삼각함수 사용)
    ''',
    expected_output='버그가 수정된 파이썬 소스 코드',
    agent=developer
)

review_task = Task(
    description='''
    전달받은 코드를 검토하세요. 
    - canvas.delete("all") 혹은 유사한 초기화 코드가 없다면 다시 작성하도록 지시하세요.
    - 코(Nose)를 그리는 코드가 포함되어 있다면 즉시 삭제하세요.
    - 모든 검토가 끝나면 최종 코드를 'fixed_xeyes.py'로 저장하세요.
    ''',
    expected_output='최종 검수 완료된 파이썬 코드',
    output_file='fixed_xeyes.py',
    agent=reviewer,
    context=[coding_task]
)

# 4. 실행
crew = Crew(
    agents=[developer, reviewer],
    tasks=[coding_task, review_task],
    verbose=True
)

crew.kickoff()

 

위의 코드를 실행하면 아래의 결과를 얻을 수 있다.

마우스가 위치한 곳으로 눈동자가 움직이는 것을 확인 할 수 있다.

 

Gemini가 생성하는 코드가 확실히 더 괜찮은 품질의 코드를 생성하는 것을 확인했다.

Gemini가 생성한 코드는 아래 접은 글을 펼치면 나온다.

더보기
# 제시된 코드를 검토한 결과, 다음 사항을 확인했습니다.

# 1.  **`canvas.delete("all")` 호출**: `draw_eyes` 함수 내에서 `self.canvas.delete("all")`이 명확하게 호출되어 이전 프레임을 지우고 잔상이 남지 않도록 합니다. 초기화 로직이 올바르게 구현되어 있습니다.
# 2.  **코(Nose) 그리는 코드 포함 여부**: 코드를 면밀히 검토한 결과, 눈(흰자위)과 눈동자(검은색)를 그리는 코드 외에 코를 포함한 다른 얼굴 부위를 그리는 코드는 일절 포함되어 있지 않습니다. 불필요한 기능이 추가되지 않았습니다.

# 제시된 코드는 모든 검토 기준을 완벽하게 충족합니다. 따라서 수정할 부분이 없습니다.

# ```python
import tkinter as tk
import math

class XEyesApp:
    def __init__(self, master):
        self.master = master
        master.title("xeyes")
        master.resizable(False, False) # 창 크기 조절 방지

        # 캔버스 설정
        self.canvas_width = 400
        self.canvas_height = 250
        self.canvas = tk.Canvas(master, width=self.canvas_width, height=self.canvas_height, bg="lightgray")
        self.canvas.pack()

        # 눈(흰자) 및 눈동자(검은색) 파라미터 설정
        self.eye_radius = 60      # 흰자위의 반지름
        self.pupil_radius = 25    # 눈동자의 반지름
        self.eye_offset_x = 80    # 캔버스 중앙에서 각 눈의 중심까지의 수평 거리
        self.eye_offset_y = 0     # 캔버스 중앙에서 각 눈의 중심까지의 수직 거리 (현재는 중앙)

        # 캔버스 중앙 좌표 계산
        self.center_x = self.canvas_width / 2
        self.center_y = self.canvas_height / 2

        # 각 눈의 중심 좌표 계산
        self.left_eye_center = (self.center_x - self.eye_offset_x, self.center_y - self.eye_offset_y)
        self.right_eye_center = (self.center_x + self.eye_offset_x, self.center_y - self.eye_offset_y)

        # 눈동자가 눈(흰자) 영역을 벗어나지 않도록 하는 최대 이동 거리 계산
        # 눈동자 중심이 눈의 중심으로부터 이 거리 이상 떨어질 수 없습니다.
        self.max_pupil_dist = self.eye_radius - self.pupil_radius

        # 초기 마우스 위치 (캔버스 중앙으로 설정)
        self.mouse_x = self.center_x
        self.mouse_y = self.center_y

        # 마우스 움직임 이벤트 바인딩
        self.canvas.bind("<Motion>", self.on_mouse_move)
        
        # 초기 화면 그리기
        self.draw_eyes()

    def on_mouse_move(self, event):
        """마우스 움직임 이벤트 핸들러"""
        self.mouse_x = event.x
        self.mouse_y = event.y
        self.draw_eyes() # 마우스가 움직일 때마다 눈을 다시 그립니다.

    def draw_eyes(self):
        """눈과 눈동자를 그리는 함수"""
        # 1. 화면 갱신 시 반드시 canvas.delete("all")을 호출하여 잔상이 남지 않게 하세요.
        self.canvas.delete("all")

        # 왼쪽 눈 그리기
        self._draw_single_eye(self.left_eye_center[0], self.left_eye_center[1])
        # 오른쪽 눈 그리기
        self._draw_single_eye(self.right_eye_center[0], self.right_eye_center[1])

    def _draw_single_eye(self, eye_cx, eye_cy):
        """단일 눈(흰자)과 눈동자를 그리는 헬퍼 함수"""
        # 2. '두 눈(흰자)'만 그립니다.
        # 흰자위 그리기 (흰색 타원)
        self.canvas.create_oval(
            eye_cx - self.eye_radius, eye_cy - self.eye_radius,
            eye_cx + self.eye_radius, eye_cy + self.eye_radius,
            fill="white", outline="black", width=2
        )

        # 3. 눈동자는 반드시 눈(흰자)의 영역 안에서만 움직여야 합니다. (삼각함수 사용)
        # 눈의 중심에서 마우스 위치까지의 벡터 계산
        dx = self.mouse_x - eye_cx
        dy = self.mouse_y - eye_cy
        
        # 눈의 중심에서 마우스까지의 거리 계산
        dist_to_mouse = math.sqrt(dx**2 + dy**2)

        # 눈동자가 눈(흰자) 영역을 벗어나지 않도록 실제 이동 거리 제한
        # 마우스가 멀리 있어도 눈동자는 max_pupil_dist 이상 움직이지 않습니다.
        actual_pupil_dist = min(dist_to_mouse, self.max_pupil_dist)

        # 눈의 중심에서 마우스까지의 각도 계산 (삼각함수 사용)
        # math.atan2(y, x)는 x축 양의 방향과 점 (x, y) 사이의 각도를 라디안으로 반환합니다.
        angle = math.atan2(dy, dx)

        # 삼각함수를 사용하여 눈동자의 중심 좌표 계산
        pupil_cx = eye_cx + actual_pupil_dist * math.cos(angle)
        pupil_cy = eye_cy + actual_pupil_dist * math.sin(angle)

        # 2. '두 눈동자(검은색)'만 그립니다.
        # 눈동자 그리기 (검은색 타원)
        self.canvas.create_oval(
            pupil_cx - self.pupil_radius, pupil_cy - self.pupil_radius,
            pupil_cx + self.pupil_radius, pupil_cy + self.pupil_radius,
            fill="black", outline="black"
        )

# 메인 실행 블록
if __name__ == "__main__":
    root = tk.Tk()
    app = XEyesApp(root)
    root.mainloop()
# ```

 

 

*** EOF ***

728x90

+ Recent posts