2025년 4월 17일 목요일

OpenAI 코딩 멀티 에이전트 Codex 오픈소스 도구 사용법

이 글은 OpenAI가 개발한 코딩 멀티 에이전트 Codex 사용법을 간략히 소개한다. 

현재 시점에서 14시간 전에 OpenAI o3, o4, codex 가 공개되었다. 모두 멀티 AI 에이전트 기능을 충실히 구현한 영상을 데모가 업로드되었고, 특히, 자동화 코딩을 지원하는 codex(코덱스) 가 로컬 컴퓨터에서 실행 가능한 형태로 공개된 점이 인상적이었다. 
OpenAI o3, o4, codex 공개 영상

코덱스 코드 및 도구는 Github에 공개되었다.
사용환경
우분투 등 리눅스 계열 혹은 윈도우즈 파워셀에서 실행해야 동작한다.  
오픈AI Key를 미리 터미널에서 설정한다.
export OPENAI_API_KEY="your-api-key-here"

파워셀에서는 다음 명령으로 설정한다. 
set OPENAI_API_KEY="your-api-key-here"

다만, 리눅스가 기본이라 윈도우즈에서는 코덱스 수행 중에 몇몇 에러가 발생할 수 있다.

코덱스 설치
터미널에서 다음을 실행해 설치 및 환경 설정을 한다. 
git clone https://github.com/openai/codex.git
cd codex
npm install -g @openai/codex

설치 시 에러가 발생하면, super user 권한으로 설치한다.
sudo npm install -g @openai/codex

설치 시 npm error code SELF_SIGNED_CERT_IN_CHAIN 에러 발생하면 다음 명령 실행 후 패키지 재설치한다.
npm config set strict-ssl false -g

코덱스 실행
터미널에서 코덱스를 실행한다. 
codex 

프롬프트를 직접 입력해도 된다.  
codex "explain this codebase to me"

만약 처음 코엑스를 실행하면, 다음과 같이 OpenAI platform 로그인이 요청된다. 

이때는 다음과 같이 로그인(Overview - OpenAI API)하고 인증한다.
로그인에 성공하면 다음과 같이 출력된다. 

앱을 개발해보자. 
codex --approval-mode full-auto "create the fanciest todo-list app"

그럼 todo list app을 codex가 개발하는 것을 확인할 수 있다. 이외에 다음과 같은 프롬프트를 입력해보자. 
"create the ascii rendering web viewer using webcam"
"create 3D tetris using three.js"


마무리
점차 AI 에이전트가 우리가 사용하는 개인 기기(노트북, 스마트폰 등)에 침투하고 있다. MCP는 그 시작인 것이고, Codex는 AI OS 위치로 맵핑하고자 한다. 올해 더 큰 경쟁과 발전이 있으리라 생각된다. 아직 codex는 실행이 불완전하지만, 

레퍼런스

2025년 4월 12일 토요일

Gemini 기반 MCP 서버 및 클라이언트 개발해 보기

이 글은 Gemini 기반 MCP 서버 및 클라이언트를 개발하는 방법을 간략히 보여준다.

MCP 기반 멀티 AI 에이전트 아키텍처 개념도

MCP의 개념과 상세한 동작 방식은 다음 글을 참고한다.

개요
MCP는 클라이언트-서버 구조를 따른다. 클라이언트는 서버의 MCP 도구를 사용하는 AI 앱이나 LLM을 의미한다. 서버는 MCP 도구를 공급하고, API, 데이터소스 인터페이스를 제공한다. 

MCP를 통해 LLM이 해결하지 못하는 작업은 외부 시스템과 연결해 서비스 받을 수 있다.

MCP서버는 파일 시스템 조작, 웹 검색, 데이터베이스 조작, 버전 관리 등 다양한 도구를 제공할 수 있다. 

제미니 LLM 기반 MCP 구조
다음은 제미니 LLM 기반 MCP 구조 예시를 보여준다. 이 예는 비행기 예약 유스케이스를 구현한다.
구조의 각 번호는 시퀀스 시나리오를 보여준다. 이 내용은 다음과 같다.
  1. MCP 호스트가 사용자 명령 입력. 예) 내일 인천에서 애틀란타 가는 비행편 찾기
    1. 클라이언트 스크립트가 입력을 처리(CLIENT.PY)
  2. 클라이언트가 MCP 서버 프로세스 시작(MCP-FLIGHT-SEARCH). STDIO 통신 채널 연결 및 관련 도구 검색
  3. 클라이언트가 사용자 명령에 대한 함수 호출 방법을 수신함
  4. 클라리언트가 함수 호출 방법에 대한 정확한 함수 호출 형식을 GEMINI에서 획득. 함수 호출 형식에 부합하는 적절한 MCP 도구를 서버에 호출. 서버의 도구 함수 호출 결과를 리턴
  5. MCP 서버가 구글 항공편 검색을 위한 SerpAPI를 호출. 구글 항공편 데이터 질의.
  6. 구글 항공편 정보 리턴
  7. 서버에서 클라이언트로 해당 정보 리턴
  8. 클라이언트가 호스로 해당 정보 전달
개발 환경
개발을 위한 최소한의 환경은 파이썬 3.8+이다. 이외 다음을 준비한다.

다음 종속성을 터미널에서 설치한다. google-genai는 google 생성AI 라이브러리이며, mcp는 MCP 서버 통신을 위한 파이썬 SDK이다. 
pip install google-genai mcp

환경변수를 설정한다. 
export GEMINI_API_KEY="your-google-api-key"
export SERP_API_KEY="your-serpapi-key"

항공편 검색 MCP 서버 설치
MCP 프로토콜 공개 이후로 많은 MCP 서버가 개발되었다. 우리는 항공편 검색 MCP 서버 오픈소스인 mcp-flgiht-search 를 사용한다. 다음을 설치한다.
pip install mcp-flight-search

코딩해보기
다음과 같이 client.py를 코딩한다. 
import os, sys, time, asyncio
from google import genai
from google.genai import types
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from dotenv import load_dotenv

load_dotenv()

gemini_api_key = os.getenv("GEMINI_API_KEY")
serp_api_key = os.getenv("SERP_API_KEY")

client = genai.Client(api_key=gemini_api_key)

server_params = StdioServerParameters(
    command="mcp-flight-search",
    args=["--connection_type", "stdio"],
    env={"SERP_API_KEY": serp_api_key},
)

async def run():
    async with stdio_client(server_params) as (read, write): # 항공 예약 검색 도구 등록
        async with ClientSession(read, write) as session:
            prompt = f"Find Flights from Atlanta to Las Vegas 2025-08-15" # 사용자 질의 명령
            await session.initialize()

            mcp_tools = await session.list_tools() # 도구 리스트 획득
            tools = [
                types.Tool(
                    function_declarations=[
                        {
                            "name": tool.name,
                            "description": tool.description,
                            "parameters": {
                                k: v
                                for k, v in tool.inputSchema.items()
                                if k not in ["additionalProperties", "$schema"]
                            },
                        }
                    ] # 해당 도구 함수 선언 생성
                )
                for tool in mcp_tools.tools
            ]

            response = client.models.generate_content(
                model="gemini-2.5-pro-exp-03-25",
                contents=prompt,
                config=types.GenerateContentConfig(
                    temperature=0,
                    tools=tools,
                ), # LLM 모델에 프롬프트 전달.
            )

            if response.candidates[0].content.parts[0].function_call:
                function_call = response.candidates[0].content.parts[0].function_call # 함수호출정보

                result = await session.call_tool(
                    function_call.name, arguments=dict(function_call.args)
                ) # 도구 함수 호출

                print("--- Formatted Result ---") # Add header for clarity
                try:
                    flight_data = json.loads(result.content[0].text)
                    print(json.dumps(flight_data, indent=2))
                except json.JSONDecodeError:
                    print("MCP server returned non-JSON response:")
                    print(result.content[0].text)
                except (IndexError, AttributeError):
                     print("Unexpected result structure from MCP server:")
                     print(result)
            else:
                print("No function call was generated by the model.")
                if response.text:
                     print("Model response:")
                     print(response.text)

asyncio.run(run()) # 클라이언트 실행

실행한다. 그럼 프롬프트에 대해 LLM이 적절한 도구와 파라메터를 확인해 함수 호출 정보를 생성한다. 이를 call_tool로 호출한 결과가 표시된다 

레퍼런스


2025년 4월 4일 금요일

Gradio HTML Javascript 렌더링 방법

이 글은 Gradio HTML Javascript 렌더링 방법을 간략히 정리한다. 가끔, HTML, javascript(자바스크립트)를 Gradio 페이지에 표시하고 싶을 때가 있다. 예를 들어, Gradio 기존 컴포넌트에서 지원되지 않는 3D 그래픽을 렌더링해야 할 때가 있다.
Gradio 3차원 모델 렌더링 예시

하지만, Gradio는 HTML만 지원하고, javascript는 다양한 이유로 사용자가 직접 Gradio에서 실행하는 것을 허용하지 않는다. 이 경우, iframe 을 사용한다.
Gradio에서 three.js 코드 실행 예시

다음은 Gradio에서 3D graphic 렌더링을 위해 three.js를 사용하는 예를 보여준다.
import os, json, requests, gradio as gr
from gradio import Interface, File, Files, Button, Label, Markdown

class 3D_viewer_component:
    def __init__(self, gr):
        self.html_code = """
<iframe srcdoc="
<!DOCTYPE html>
<html>
  <head>
    <style>
      body, html { margin: 0; overflow: hidden; height: 100%; }
      canvas { display: block; }
    </style>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js'></script>
  </head>
  <body>
    <script>
const WIDTH = window.innerWidth;
const HEIGHT = window.innerHeight;

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(WIDTH, HEIGHT);
renderer.setClearColor(0x111111, 1);
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT, 0.1, 10000);
camera.position.z = 30;
camera.position.y = 10;
scene.add(camera);

const boxGeometry = new THREE.BoxGeometry(10, 10, 10);
const basicMaterial = new THREE.MeshBasicMaterial({ color: 0x0095dd });
const cube = new THREE.Mesh(boxGeometry, basicMaterial);
cube.position.x = -25;
cube.rotation.set(0.4, 0.2, 0);
scene.add(cube);

const torusGeometry = new THREE.TorusGeometry(7, 1, 16, 32);
const phongMaterial = new THREE.MeshPhongMaterial({ color: 0xff9500 });
const torus = new THREE.Mesh(torusGeometry, phongMaterial);
torus.rotation.set(0.5, 0.5, 0);
scene.add(torus);

const light = new THREE.PointLight(0xffffff);
light.position.set(-10, 15, 50);
scene.add(light);

let t = 0;
function render() {
  t += 0.01;
  requestAnimationFrame(render);
  cube.rotation.y += 0.01;
  torus.scale.y = Math.abs(Math.sin(t));
  renderer.render(scene, camera);
}
render();
    </script>
  </body>
</html>
" width="100%" height="100%" style="border:none;"></iframe>
        """

        self.component = gr.HTML(self.html_code)

with gr.Blocks(title='3D viewer') as interface:
gr.Markdown("# 3D viewer")
with gr.Row(equal_height=True):
3D_viewer_component(gr)
3D_viewer_component(gr)

interface.launch(share=True)

참고로, Gradio에서 공식적으로 추천하는 방법은 다음과 같이 커스텀 컴포넌트를 개발한 것이다.
커스텀 컴포넌트 생성 명령 예시
명령 실행 순서(일부)

위 그림과 같이 컴포넌트 생성하면, 다음처럼 프로젝트 파일이 생성되고, 이를 수정해 개발하는 방식이다.
- backend/ <- The python code for your custom component
- frontend/ <- The javascript code for your custom component
- demo/     <- A sample app using your component. Modify this!
- pyproject.toml <- Used to build the package and specify package metadata.
Gradio 커스텀 컴포넌트 예시(Gradio Custom Components Gallery)

이외 3차원 모델 파일 뷰어는 Model3D 컴포넌트를 사용할 수도 있다.
Model3D 예시

레퍼런스