오늘은 Microsoft에서 만든 멀티 에이전트 프레임워크인 AutoGen을 로컬 환경에서 테스트해 본 경험을 공유하려고 합니다. 특히 최근에 나온 Gemma3 모델을 로컬 Ollama로 띄우고, AutoGen의 FastAPI 예제와 연동하는 과정을 다뤄보겠습니다.
결론부터 말씀드리면, 아직 공식적으로 지원하지 않는 모델이라 약간의 라이브러리 코드 수정이 필요했습니다. 그 전체 과정을 차근차근 따라가 보시죠!
먼저 AutoGen 공식 GitHub 레포지토리에서 FastAPI 예제 코드를 준비합니다. 아래 경로에서 코드를 확인하고 다운로드할 수 있습니다. 저희는 이 중에서 app_agent.py
를 사용할 겁니다.
다음으로, 예제 실행에 필요한 파이썬 패키지들을 pip
으로 설치합니다.
pip install -U "autogen-agentchat" "autogen-ext[openai]" "autogen-ext[ollama]" "fastapi" "uvicorn[standard]" "PyYAML"
프로젝트 루트 디렉터리에 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}'
이제 모든 준비가 끝났으니 서버를 실행해 봅니다. 터미널에서 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
로 이어진 것을 알 수 있습니다.
원인은 간단했습니다. 현재 제가 설치한 버전의 AutoGen 라이브러리가 gemma3
모델을 공식적으로 지원하지 않기 때문입니다. AutoGen 공식 문서를 확인해 봐도 지원하는 family
리스트에 gemma3
는 포함되어 있지 않았습니다.
이런 경우, 라이브러리가 업데이트될 때까지 기다리거나 직접 코드를 수정해서 모델을 추가하는 방법이 있습니다. 저희는 후자를 택했습니다. 가상환경에 설치된 AutoGen 라이브러리 파일을 직접 수정하여 gemma3
모델 정보를 추가해 보겠습니다.
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_core
의ModelFamily
클래스에GEMMA_3 = "gemma3"
와 같이 상수를 추가하여 관리하는 것도 좋은 방법입니다.
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)
위와 같이 라이브러리와 예제 코드를 수정한 뒤 서버를 다시 실행하고 메시지를 보내면... 드디어 정상적으로 로컬 Gemma3 모델이 응답을 생성합니다!
AutoGen의 FastAPI 예제를 로컬 Gemma3 모델과 연동하면서 발생한 문제를 해결하는 과정을 공유해 보았습니다. 최신 모델을 사용하다 보면 이처럼 라이브러리가 아직 지원하지 않는 경우가 종종 발생하는데, 오픈소스 프로젝트는 직접 코드를 수정해서 빠르게 대응해 볼 수 있다는 점이 큰 매력인 것 같습니다.
이 글이 저처럼 새로운 모델을 AutoGen에 적용해 보려는 분들께 작은 도움이 되었으면 좋겠습니다. 감사합니다!