로컬 Gemma3 모델로 AutoGen FastAPI 예제 돌려보기 (feat. 삽질 및 해결 과정)

Dev Smile·2025년 7월 28일
1
post-thumbnail

오늘은 Microsoft에서 만든 멀티 에이전트 프레임워크인 AutoGen을 로컬 환경에서 테스트해 본 경험을 공유하려고 합니다. 특히 최근에 나온 Gemma3 모델을 로컬 Ollama로 띄우고, AutoGen의 FastAPI 예제와 연동하는 과정을 다뤄보겠습니다.

결론부터 말씀드리면, 아직 공식적으로 지원하지 않는 모델이라 약간의 라이브러리 코드 수정이 필요했습니다. 그 전체 과정을 차근차근 따라가 보시죠!


1. 테스트 환경 준비

1.1. FastAPI 예제 코드 다운로드

먼저 AutoGen 공식 GitHub 레포지토리에서 FastAPI 예제 코드를 준비합니다. 아래 경로에서 코드를 확인하고 다운로드할 수 있습니다. 저희는 이 중에서 app_agent.py를 사용할 겁니다.

1.2. 필요 라이브러리 설치

다음으로, 예제 실행에 필요한 파이썬 패키지들을 pip으로 설치합니다.

pip install -U "autogen-agentchat" "autogen-ext[openai]" "autogen-ext[ollama]" "fastapi" "uvicorn[standard]" "PyYAML"

1.3. 모델 설정 파일 추가

프로젝트 루트 디렉터리에 model_config.yaml 파일을 생성하고, 로컬에서 실행 중인 Ollama의 Gemma3 모델을 사용하도록 설정합니다. {my-local-gemma3-model} 부분은 실제 Ollama 호스트 주소(예: http://localhost:11434)로 변경해주세요.

# Use Ollama with Gemma3
provider: autogen_ext.models.ollama.OllamaChatCompletionClient
config:
  model: gemma3:27b
  host: '{my-local-gemma3-model}'

2. 실행 및 에러 발생

이제 모든 준비가 끝났으니 서버를 실행해 봅니다. 터미널에서 uvicorn app_agent:app --port 8001 --reload 명령어로 서버를 실행하고 http://localhost:8001로 접속하면 간단한 채팅 UI가 나타납니다.

하지만 메시지를 보내는 순간... 500 Internal Server Error가 발생합니다!

서버 로그를 확인해 보면 더 자세한 에러 내용을 볼 수 있습니다.

핵심 에러 메시지는 다음과 같습니다.

[Error] Traceback (most recent call last):
  ...
KeyError: 'gemma3'

...
ValueError: model_info is required when model name is not a valid OpenAI model

에러 로그를 보니 _MODEL_INFO 딕셔너리에서 'gemma3'라는 키를 찾지 못해 KeyError가 발생했고, 이로 인해 모델 정보를 제대로 불러오지 못해 ValueError로 이어진 것을 알 수 있습니다.


3. 원인 분석 및 해결

3.1. 원인: 공식 미지원 모델

원인은 간단했습니다. 현재 제가 설치한 버전의 AutoGen 라이브러리가 gemma3 모델을 공식적으로 지원하지 않기 때문입니다. AutoGen 공식 문서를 확인해 봐도 지원하는 family 리스트에 gemma3는 포함되어 있지 않았습니다.

3.2. 해결책: 라이브러리 코드 직접 수정

이런 경우, 라이브러리가 업데이트될 때까지 기다리거나 직접 코드를 수정해서 모델을 추가하는 방법이 있습니다. 저희는 후자를 택했습니다. 가상환경에 설치된 AutoGen 라이브러리 파일을 직접 수정하여 gemma3 모델 정보를 추가해 보겠습니다.

1. autogen_ext\models\ollama\_model_info.py 수정

Ollama 클라이언트가 모델 정보를 참조하는 이 파일에 gemma3 정보를 추가합니다.

  • _MODEL_INFO 딕셔너리에 모델의 특징(비전, 함수 호출 지원 여부 등)을 추가합니다.
  • _MODEL_TOKEN_LIMITS 딕셔너리에 모델의 컨텍스트 길이를 추가합니다.
# autogen_ext\models\ollama\_model_info.py

_MODEL_INFO: Dict[str, ModelInfo] = {
    # ... 다른 모델 정보들
    "gemma3": {
        "vision": False,
        "function_calling": False,
        "json_output": True,
        "family": "gemma3", # family를 직접 지정해줄 수 있습니다.
        "structured_output": True,
    },
    # ...
}

_MODEL_TOKEN_LIMITS: Dict[str, int] = {
    # ... 다른 모델 토큰 정보들
    "gemma3": 8192,
    # ...
}

참고: 위 코드에서 family를 추가하고 ModelFamily.UNKNOWN 대신 문자열 "gemma3"를 사용했습니다. 더 명확하게 모델을 구분하기 위함이며, autogen_coreModelFamily 클래스에 GEMMA_3 = "gemma3"와 같이 상수를 추가하여 관리하는 것도 좋은 방법입니다.

2. app_agent.py 코드 수정

기존 예제 코드는 OpenAI 모델을 기본으로 가정하는 부분이 있어, Ollama 클라이언트를 명시적으로 사용하도록 코드를 수정해야 합니다.

  • autogen_core.models.ChatCompletionClient 대신 autogen_ext.models.ollama.OllamaChatCompletionClient를 임포트합니다.
  • get_agent 함수 내에서 OllamaChatCompletionClient.load_component(model_config)를 호출하여 모델 클라이언트를 로드합니다.

아래는 수정한 전체 app_agent.py 코드입니다.

import json
import os
import traceback
from datetime import datetime
from typing import Any

import aiofiles
import yaml
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.messages import TextMessage
from autogen_core import CancellationToken
# Ollama 클라이언트를 직접 임포트합니다.
from autogen_ext.models.ollama import OllamaChatCompletionClient 

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles

app = FastAPI()

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.mount("/static", StaticFiles(directory="."), name="static")

@app.get("/")
async def root():
    return FileResponse("app_agent.html")

model_config_path = "model_config.yaml"
state_path = "agent_state.json"
history_path = "agent_history.json"

async def get_agent() -> AssistantAgent:
    """Get the assistant agent, load state from file."""
    async with aiofiles.open(model_config_path, "r") as file:
        model_config = yaml.safe_load(await file.read())
    # Ollama 클라이언트를 명시적으로 로드합니다.
    model_client = OllamaChatCompletionClient.load_component(model_config)
    agent = AssistantAgent(
        name="assistant",
        model_client=model_client,
        system_message="You are a helpful assistant.",
    )
    if not os.path.exists(state_path):
        return agent
    async with aiofiles.open(state_path, "r") as file:
        state = json.loads(await file.read())
    await agent.load_state(state)
    return agent

# (이하 코드는 원본과 거의 동일)
# get_history, history, chat 함수 ...
# ...

@app.post("/chat", response_model=TextMessage)
async def chat(request: TextMessage) -> TextMessage:
    try:
        agent = await get_agent()
        response = await agent.on_messages(messages=[request], cancellation_token=CancellationToken())

        state = await agent.save_state()
        async with aiofiles.open(state_path, "w") as file:
            await file.write(json.dumps(state))

        history = await get_history()
        
        class DateTimeEncoder(json.JSONEncoder):
            def default(self, obj):
                if isinstance(obj, datetime):
                    return obj.isoformat()
                return super().default(obj)
        
        request_dict = request.model_dump()
        response_dict = response.chat_message.model_dump()
        
        history.append(request_dict)
        history.append(response_dict)
        async with aiofiles.open(history_path, "w") as file:
            await file.write(json.dumps(history, cls=DateTimeEncoder))

        assert isinstance(response.chat_message, TextMessage)
        return response.chat_message
    except Exception as e:
        error_message = {
            "type": "error",
            "content": f"Error: {str(e)}",
            "source": "system"
        }
        print("[Error]", traceback.format_exc())
        raise HTTPException(status_code=500, detail=error_message) from e

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8001)

4. 테스트 성공!

위와 같이 라이브러리와 예제 코드를 수정한 뒤 서버를 다시 실행하고 메시지를 보내면... 드디어 정상적으로 로컬 Gemma3 모델이 응답을 생성합니다!

마무리

AutoGen의 FastAPI 예제를 로컬 Gemma3 모델과 연동하면서 발생한 문제를 해결하는 과정을 공유해 보았습니다. 최신 모델을 사용하다 보면 이처럼 라이브러리가 아직 지원하지 않는 경우가 종종 발생하는데, 오픈소스 프로젝트는 직접 코드를 수정해서 빠르게 대응해 볼 수 있다는 점이 큰 매력인 것 같습니다.

이 글이 저처럼 새로운 모델을 AutoGen에 적용해 보려는 분들께 작은 도움이 되었으면 좋겠습니다. 감사합니다!

0개의 댓글