🔁 OpenAI 기반 MCP 에이전트를 Gemini 기반으로 바꾸는 실전 사례
최근 다양한 LLM(대형 언어 모델)이 등장하면서, 기존 OpenAI 기반 프로젝트를 다른 모델(Google Gemini, Claude, Mistral 등)로도 유연하게 실행할 수 있는 구조를 갖추는 것이 매우 중요해졌습니다.
이번 글에서는 제가 직접 실습해 본 MCP (Model Context Protocol)
기반 Youtube 추천 에이전트를 OpenAI에서 Google Gemini로 교체한 과정을 공유하고자 합니다.
- 기존 코드:
mcp_client.py
(OpenAI 기반) - 새 코드:
mcp_client_gemini.py
(Google Gemini 기반)
기존 코드는 https://github.com/dabidstudio/python_mcp_agent 참조했습니다
AI 에이전트 개발은 이제 특정 모델에 종속되지 않고, 다양한 LLM을 상황에 따라 유연하게 사용하는 능력이 중요해졌습니다. 특히 API 기반으로 동작하는 Gemini, Claude, Mistral 등도 OpenAI 못지 않게 빠르게 발전하고 있기 때문에, 코드를 모델 간에 이식 가능하도록 구조화하는 연습이 중요하다고 생각했습니다.
🧩 MCP 구조 간단 소개
MCP는 LLM 기반 시스템에서 에이전트가 외부 도구(function/tool) 와 통신할 수 있도록 해주는 구조입니다. 예를 들어 사용자가 유튜브 영상을 추천해달라고 요청하면, LLM은 search_youtube_videos()
같은 도구를 호출하고, 그 결과를 다시 사용자에게 자연어로 출력합니다.
MCP 구성 요소는 다음과 같습니다:
MCPClient
: LLM과 대화하고, 필요한 도구 호출을 중계MCPServer
: 도구(function)를 실행하는 서버Tool
: 에이전트가 사용할 수 있는 기능 목록 (예: 유튜브 검색)
📄 기존 코드: mcp_client.py
(OpenAI 기반)
OpenAI 기반 클라이언트는 다음과 같은 흐름으로 동작합니다:
- 사용자 입력을 OpenAI GPT에 전달
- GPT가 도구 호출(function_call)을 응답에 포함
- 클라이언트가 해당 도구를 MCP 서버에 호출
- 도구 결과를 GPT에 다시 전달하여 최종 응답 생성
- 응답을
stream=True
옵션으로 스트리밍 출력
핵심 코드 요약
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools,
tool_choice="auto",
stream=True,
)
- tools: OpenAI에 도구 명세를 JSON Schema 기반으로 전달
- stream=True: 응답이 토큰 단위로 실시간 출력됨
🔁 전환 목표: Gemini 기반으로 교체
이제 동일한 기능을 Gemini 기반에서 수행하도록 바꿔보겠습니다. 핵심 목표는 다음과 같습니다:
- OpenAI 의존성을 제거하고 Gemini API 활용
- MCP 서버와의 도구 호출 연동 유지
- 응답을 자연어로 처리하는 흐름 유지
🔨 수정 코드:
mcp_client_gemini.py
1. Gemini 초기화
Gemini는 google.generativeai 패키지를 통해 사용할 수 있습니다. API 키를 환경변수로 설정한 후, 모델을 초기화합니다.
from google import generativeai as genai
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
model = genai.GenerativeModel(model_name="gemini-1.5-pro-latest")
2. MCP 도구 → Gemini 도구로 변환
OpenAI에서는 tool_call 명세를 JSON으로 직접 전달하지만, Gemini에서는 types.Tool() 형태로 선언해야 합니다.
from google.generativeai.types import Tool
tools = [
Tool.from_function_declaration(tool.openai_schema)
for tool in mcp_tools.tools
]
- openai_schema: 기존 MCP 도구가 JSON Schema로 선언된 형식
- Gemini는 이 스키마를 그대로 호환하므로 손쉽게 재활용 가능
3. 입력 구성 및 콘텐츠 생성
Gemini는 generate_content() 메서드를 통해 입력을 처리합니다. 이때 도구 정보도 함께 전달해야 합니다.
response = model.generate_content(
contents=[{"role": "user", "parts": [prompt]}],
tools=tools,
tool_config={"function_calling_config": {"mode": "auto"}},
)
- contents: 대화 이력 입력 (OpenAI의 messages에 해당)
- tools: 사용할 도구 목록
- function_calling_config: 도구 호출을 자동으로 처리하도록 설정
4. 함수 호출 감지 및 실행
Gemini의 응답에서 도구 호출이 포함된 경우 다음과 같이 감지할 수 있습니다:
candidate = response.candidates[0]
if candidate.content.parts[0].function_call:
func_call = candidate.content.parts[0].function_call
그 후, MCP 서버에 해당 도구를 호출하고 결과를 받아 모델에 다시 전달할 수 있습니다. (이 부분은 선택 사항이며, 이번 실험에서는 간단히 도구 호출과 출력만 구현)
⚠️ 마주친 이슈와 해결 전략
1. ❌ 스트리밍 미지원
문제:
OpenAI에서는 stream=True로 토큰 단위 출력이 가능했지만, Gemini API는 현재 이 기능을 지원하지 않습니다.
해결 방안:
- 전체 응답을 받은 뒤 한 번에 출력하는 방식 사용
- 프론트엔드에서 출력 도중 텍스트를 한 줄씩 나누어 표시하는 UX 구현으로 유사한 경험 제공
해당 문제는 꼭 필요한 것이 아니라서 직접 구현하지는 않았습니다.
2. ❌ 썸네일 이미지가 Markdown에서 보이지 않고, 출력 결과가 이상함
문제:
st.markdown()으로 OpenAI 응답을 출력하면 썸네일 이미지가 잘 보이는데, Gemini 응답은 동일한 마크다운이 코드 블럭 안처럼 렌더링되어 이미지가 보이지 않음 그리고 MCP server에서의 출력 내용이 json 형태이기 때문에 그대로 출력하는 것은 이상함
원인:
OPENAI의 agent는 mcp server에서의 응답을 한번 더 llm을 타서 정제된 형태로 출력을 제공하는 기능이 있음.
해결 방안:
- 관련해서 정상적으로 생성할 수 있도록 Prompt Engineering 활용
content_results = []
for content in result.content:
content_results.append(json.loads(content.text))
followup_prompt = f'''
아래 "결과:" 이후 내용은 는 YouTube 영상 검색 결과를 json 형식의 list로 받은 것입니다.
당신은 "결과:" 이후 내용을 모두 마크다운 형식으로 변환해주세요
다만 이미지 링크가 있는 항목의 경우 해당 내용의 가장 위쪽에 이미지 형태로 출력해주세요
HTML 태그는 사용하지 마세요
-----------------
결과 : {content_results}
'''
final_response = client.models.generate_content(
model="gemini-2.0-flash", # flash도 가능
contents=followup_prompt,
config=types.GenerateContentConfig(
temperature=0,
),
)
response_text += final_response.text
🧪 테스트 예시
- 기존 코드처럼 결과를 잘 생성하는 것을 확인 하였습니다.