2025년 4월 17일 목요일

OpenAI 바이브 코딩 지원 멀티 에이전트 Codex 도구 사용법

이 글은 OpenAI가 개발한 바이브 코딩(vibe coding)을 지원하는 멀티 에이전트 Codex 사용법을 간략히 소개한다. 이 글은 얼마전 ChatGPT Pro 버전에 무료로 오픈된 Codex와 오픈소스 Codex 버전(CLI) 각 사용법을 모두 설명한다.
소개
현재 시점(4월17일)에서 14시간 전에 OpenAI o3, o4, codex 가 공개되었다. 모두 멀티 AI 에이전트 기능을 충실히 구현한 영상을 데모가 업로드되었고, 특히, 자동화 코딩을 지원하는 codex(코덱스) 가 로컬 컴퓨터에서 실행 가능한 형태로 공개된 점이 인상적이었다. 
OpenAI o3, o4, codex 공개 영상

Codex는 단순한 코드 생성에 그치지 않고, 버그 수정, 테스트 실행, 코드 리뷰 제안 등 복잡한 개발 업무를 자동화한다. 각 작업은 사용자의 코드 저장소가 사전 로드된 격리된 클라우드 샌드박스 환경에서 독립적으로 실행되며, 작업의 복잡도에 따라 1분에서 30분 이내에 결과를 제공한다. 또한, Codex는 작업 수행 과정에서 생성된 터미널 로그와 테스트 출력 등의 증거를 제공하여, 사용자가 변경 사항을 추적하고 검토할 수 있도록 지원한다.

코덱스 코드 및 도구는 Github에 공개되었다.

6월초에는 ChatGPT pro 사용자에게 codex 기능이 공개되었다. Codex는 ChatGPT의 사이드바를 통해 접근할 수 있으며, 사용자는 자연어로 코딩 작업을 지시하거나 기존 코드에 대한 질문을 할 수 있다. 또한, Codex는 사용자의 개발 환경과 유사하게 구성할 수 있어, 실제 개발 환경과의 통합이 용이하다. 보안 측면에서도 Codex는 격리된 환경에서 실행되며, 인터넷 접근은 기본적으로 비활성화되어 있다. 필요한 경우 특정 도메인에 대한 접근을 허용할 수 있으며, 이를 통해 외부 리소스를 사용하는 테스트나 패키지 설치 등이 가능하다.

Codex는 현재 ChatGPT Pro, Team, Enterprise 사용자에게 제공되며, Plus 및 Edu 사용자에게도 점차 확대되고 있다. 또한, Codex CLI를 통해 터미널 환경에서도 Codex의 기능을 활용할 수 있어, 다양한 개발 환경에서의 활용이 가능하다.(openai.com)

ChatGPT에서 코덱스 사용법

Codex를 활용한 전체 사용 과정은 단순한 코드 자동 생성 수준을 넘어, 실제 소프트웨어 개발의 전 과정을 자연어 기반으로 자동화하는 방식으로 개발되어 있다. 

Codex 는 현재 github를 기본 연결해 사용하도록 되어 있어, 다음 본인의 github 프로젝트를 연결해 실습을 진행했음을 밝힌다. 

  • https://github.com/mac999/AI_agent_simple_function_call.git

프로젝트 시작: 코드 저장소 구성 및 환경 연결

ChatGPT pro 좌측 메뉴에서 다음과 같이codex를 실행하면, 연결할 github 계정 및 저장소를 요청한다. 

다음과 같이 본인의 github 계정을 연결한다.


참고로, codex는 여러 github 저장소를 다음과 같이 연결, 관리 지원하므로, 바이브 코딩하길 원하는 저장소를 선택해 프롬프트를 입력하면 된다. 

codex는 기본적으로 github 저장소가 연결된 후 처음 시작할 때, 다음 같이 해당 코드 저장소를 자동으로 분석한다. 이후, 기능 개선을 위한 솔류션, 문제점 등을 진단해 제안해준다.


메뉴 중에 'ask', 'code가 채팅창에 붙어 있는 데, ask는 질문하고 답변을 얻는 데 사용하는 반면, code는 직접 github 저장소에 답변에 대한 솔류션 파일들을 생성, 수정하는 데 사용된다. 

자동 제안된 작업 중에 'Find and propose tasks for typo, bug, comments and test improvements'을 선택한다. 그럼 다음과 같은 상세 작업들을 codex가 설명해 준다. 

처리 요청을 하면, 코덱스는 llm_func_call_for_ai_agent.py 같은 주요 파일과 README.md, tests/ 디렉토리를 포함하는 초기 구조를 설정한다. 코덱스가 생성한 코드 수정 전/후를 확인한 후 PR(Pull Request)를 요청한다.

그럼, 다음과 같이, 해당 github 프로젝트에 PR이 요청되고, Merge Pull Request를 실행하면, 해당 저장소에 코덱스가 생성한 파일이 병합 및 추가된다. 

원본 github 저장소에 보면, 해당 파일이 업데이트되었고, 추가된 tests 폴더가 있는 것을 확인할 수 있다. 

자연어 명령을 통한 코드 작업 지시

Codex에 "코드베이스에서 버그를 찾아 수정해줘"와 같은 제안된 작업을 실행하면, Codex는 GitHub의 코드 구조를 분석해 변경 가능성이 있는 파일을 찾아낸다. 

이 실습에서는 llm_func_call_for_ai_agent.py 파일 내부의 preprocess_code 함수에 NoneType 예외가 발생할 수 있는 위험을 식별했다.

Codex는 이 문제를 다음과 같이 자동 해결한다.

  • re.search()의 결과가 None일 경우를 명시적으로 처리함.

  • 오류 메시지를 출력하고 return ""을 통해 안전하게 함수 종료.

  • 이 변경은 GitHub의 diff 뷰 상에서도 명확히 나타난다 (빨간색 삭제 라인과 초록색 추가 라인으로 표시됨).

또한, 이러한 수정은 Pull Request 형식으로 자동 정리된다. 사용자는 Codex가 제안한 수정을 검토하고 GitHub에서 수동으로 머지할 수 있으며, 필요시 코멘트나 수정 작업도 추가로 요청할 수 있다.

테스트 코드 자동 생성 및 실행

Codex는 코드 수정 후 테스트가 없다는 것을 감지하고, tests/test_preprocess_code.py 라는 유닛 테스트 스크립트를 새로 생성한다. 이 테스트는 unittest 프레임워크를 기반으로 구성되며, 문제의 함수(preprocess_code)가 다양한 입력에 대해 안정적으로 동작하는지를 확인한다.

사용자는 명시된 명령어인

python3 -m unittest tests/test_preprocess_code.py -v

를 통해 테스트를 실행하면 된다. 테스트가 성공적으로 통과하면, 해당 Pull Request에 테스트 결과가 함께 기록된다.

또한 ChatGPT Codex 웹 환경에서는 각 작업의 테스트 상태, 문제 발생 여부, 해결 내역까지 추적되며, 모든 변경 사항이 히스토리로 남는다.

에이전트 생성 기능 활용

Codex는 기능을 자동 생성하여 관련 소스 파일들을 추가할 수 있다. 이 예에서는 "ReAct 및 OpenAI API를 사용해서 에이전트를 만들어줘"라는 지시를 주었고, 이에 따라 Codex는 react_agent.py 파일을 새로 생성하였다.

이 파일에는 다음과 같은 핵심 기능이 포함된다:

  • ReAct 방식의 reasoning loop를 구성해, LLM이 도구를 통해 사용자 명령을 처리할 수 있도록 함.

  • OpenAI의 ChatCompletion API를 통해 tool_calls 응답을 받아 동적으로 searchcalculator 등 도구를 호출.

  • CLI 환경에서 single query 모드 또는 chatbot 모드로 작동할 수 있도록 구성.

  • JSON 기반의 tool 호출 구조를 자동 파싱하여 함수 매핑에 전달.

이 코드는 실제로 CLI에서 실행 가능한 상태로, 사용자 질문에 따라 LLM이 적절한 함수를 찾아 실행하고 응답을 반환하는 구조를 완성한다.

이제 터미널에서 git pull 로 해당 생성된 코드를 로컬에 다운로드 받고, 로컬에서 필요한 작업들을 하면 된다.

Codex의 작업 관리 및 통합

Codex는 모든 작업 내역을 시간 순으로 정리하여 사용자에게 제공한다. 작업 목록에는 작업 이름, 처리된 커밋 수, 변경된 라인 수, 테스트 결과, 완료 여부(Merged) 등이 정리되어 있다. 이렇게 정리된 정보는 사용자 또는 팀이 코드 리뷰와 품질 관리를 효율적으로 수행할 수 있도록 한다.

Codex가 수행한 작업은 단순한 텍스트 생성이 아니라, 다음과 같은 고급 개발 활동을 포함한다.

  • 문제 코드의 의미론적 분석

  • 실제 코드 수정

  • 기능 보완

  • 테스트 자동화

  • CLI 도구 설계

  • Pull Request 생성 및 병합

이는 단순한 코드 제안 도구를 넘어선 AI 기반의 대화형 코드 리팩토링 및 에이전트 생성 플랫폼으로 동작된다.

오픈소스 코덱스 사용법
사용환경
오픈소스 코덱스는 Codex CLI(command line interface)버전으로 알려져 있다. 우분투 등 리눅스 계열 혹은 윈도우즈 파워셀에서 실행해야 제대로 동작한다. 아울러, 오픈AI Key를 미리 터미널에서 설정해야 동작된다(ChatGPT Pro 이상 지원). 
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는 사용자는 자연어로 고수준의 요구사항을 전달하면, 개발자의 코드베이스를 직접 분석하고, 문제를 찾아내며, 기능을 보완하고, 테스트 코드를 작성한 뒤, 그 결과를 Pull Request로 정리하여 통합하는 등의 전생애주기 개발 프로세스 자동화를 지향하고 있다. 
바이브 코딩에 의한 Codex 프로젝트 생성

레퍼런스
부록
최근 Copilot, Cursor 등이 Agent 모드를 추가함으로써 codex를 직접사용하지 않고, vscode같은 IDE에서도 직접 이를 이용할 수 있다. 다음은 이 예시를 보여준다.

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 예시

레퍼런스