OpenAI API로 구현하는 AI Agent #2, “프로그래밍 AI 에이전트”

지난 글에서 OpenAI API로 구현하는 AI Agent에 대해 설명했습니다. 오늘은 이전 글의 내용을 기초로 하여 조금 더 구체적인 Agent 기능을 구현해 보겠습니다. 이전 글은 아래 링크로 확인하세요.



OpenAI API로 구현하는 AI Agent #2, “프로그래밍 AI 에이전트”

이번에는 실제 Agent를 구현하는 방법에 대해 정리해 보겠습니다. 그러기 위해서 사용자가 대략적인 목적을 제시하면, AI 에이전트가 이를 구체화하기 위해 단계별로 질문을 하고, 사용자의 답변을 기반으로 작업을 진행하는 AI 에이전트(‘프로그래밍 AI 에이전트’)를 구현해 보도록 하겠습니다.


AI 에이전트의 목표와 기본 구조 설계

프로그래밍 AI 에이전트는 사용자가 “웹사이트를 만들고 싶어요” 같은 모호한 요청을 했을 때, 이를 구체적인 요구사항으로 바꾸고, 단계별로 필요한 코드를 제공해야 합니다. 이를 위해 에이전트는 다음과 같은 기능을 정의합니다.

  • 사용자와의 대화: 사용자의 의도를 파악하기 위해 질문을 던지고 답변을 수집.
  • 요구사항 구체화: 모호한 요청을 세부적인 작업으로 분해.
  • 코드 생성: 확정된 요구사항을 기반으로 실행 가능한 코드를 생성.
  • 작업 관리: 대화 흐름과 에이전트 간 전환을 체계적으로 관리.

이제 이러한 것들을 구현하려면 OpenAI API의 Agents SDK를 활용해서 사용자와의 대화 흐름을 관리하고, 도구(Tools) 호출과 핸드오프 기능을 사용해 작업을 체계화해야 합니다. 이제 이러한 것들을 구현하기 위해 OpenAI API와 Agents SDK를 활용하고, 특히 핸드오프(Handoff) 기능을 사용해 작업을 단계별로 넘기는 구조를 설계해 보겠습니다.

메인 에이전트 (사용자의 의도 파악)

메인 에이전트는 사용자의 초기 요청을 받고, 이를 구체화하기 위한 질문을 던지는 역할을 합니다. 예를 들어, 사용자가 “웹사이트를 만들고 싶어요”라고 말하면, AI 에이전트는 “어떤 기능을 추가하고 싶으신가요?” 같은 질문을 던져 의도를 파악해 나가야 합니다. 하지만, 실제로 대화 진행은 설정한 모델(코드상에서는 GPT-4o)에 따라 다르게 반응할 거예요.

main_agent = Agent(
    name="MainSupportAgent",
    model="gpt-4o",
    instructions="당신은 사용자의 요청을 구체화하는 설계자입니다. 사용자가 원하는 프로그램이 무엇인지 파악해야 합니다. 간단한 답변을 유도하기 위해 몇 가지 예시를 제시하세요. (예, 웹사이트, 웹 앱, 게임 등) 모호한 요청은 사용자에게 질문하여 세부사항을 파악하세요.",
)

이 에이전트는 사용자의 입력을 분석하고, 필요하면 다음 단계로 작업을 넘기도록 구성되었어요.

요구사항 수집 (세부사항 확정)

사용자가 “로그인 기능이 있는 웹사이트”라고 대답했다면, 요구사항 수집 에이전트가 세부사항을 추가로 더 파악합니다. 예를 들어, “데이터베이스를 사용할 건가요?” 같은 질문을 추가로 던질 수 있겠죠.

여기서 Handoff를 사용해 메인 에이전트가 요구사항 수집 에이전트로 작업을 넘기도록 설정했습니다. 사용자의 답변에 따라 요구사항을 단계적으로 확정해 나갑니다.

requirements_agent = Agent(
    name="RequirementsAgent",
    model="gpt-4o",
    instructions="사용자의 답변을 바탕으로 요구사항을 구체화합니다. 추가 질문을 통해 세부사항을 확정하세요.",
)

여기서 핸드오프(Handoff)를 사용해 메인 에이전트가 요구사항 수집 에이전트로 작업을 넘기도록 구성되었어요. 핸드오프는 비동기 함수로 구현되어 대화 흐름이 자연스럽게 이어지죠.

async def collect_handoff_handler(context_wrapper, arguments):
    await asyncio.sleep(0)
    return requirements_agent

collect_handoff = Handoff(
    tool_name="collect_requirements",
    tool_description="요구사항을 수집하기 위해 추가 질문을 던지는 도구",
    on_invoke_handoff=collect_handoff_handler,
    agent_name=requirements_agent.name,
)
main_agent.handoffs.append(collect_handoff)


코드 생성 에이전트

요구사항이 확정되면 코드 생성 에이전트가 동작합니다. 이 에이전트는 사용자가 요청한 기능을 기반으로 실행 가능한 코드를 생성합니다. 예를 들어, “로그인 기능에 SQLite를 사용”이라는 요구사항이 확정되면 관련 코드를 제공합니다.

code_agent = Agent(
    name="CodeGenerationAgent",
    model="gpt-4o",
    instructions="확정된 요구사항을 바탕으로 실행 가능한 코드를 작성합니다.",
)

마찬가지로 핸드오프를 통해 요구사항 수집 에이전트에서 코드 생성 에이전트로 작업이 넘어갑니다.

async def generate_code_handoff_handler(context_wrapper, arguments):
    await asyncio.sleep(0)
    return code_agent

generate_code_handoff = Handoff(
    tool_name="generate_code",
    tool_description="요구사항을 바탕으로 코드를 생성하는 도구",
    on_invoke_handoff=generate_code_handoff_handler,
    agent_name=code_agent.name,
)
requirements_agent.handoffs.append(generate_code_handoff)

AI 에이전트 실행 및 대화 관리

모든 에이전트를 연결해 사용자가 입력을 제공하면 대화가 자연스럽게 진행되도록 설정했습니다. run_programming_support_agent 함수는 사용자의 입력을 받아 에이전트를 호출하고, 콘텍스트를 유지하며 작업을 단계적으로 진행합니다.

async def run_programming_support_agent():
    context = {}  # 대화 상태 유지
    current_agent = main_agent  # 초기 에이전트
    
    while True:
        user_input = input("\n사용자: ")
        if user_input.lower() in ['exit', 'quit']:
            print("대화를 종료합니다.")
            break
        
        print(f"사용자: {user_input}")
        result = await interact_with_agent(current_agent, user_input, context)
        
        if result.final_output:
            print(f"\nAI ({current_agent.name}): {result.final_output}")
        
        # 다음 에이전트로 전환
        if result._last_agent.name == "MainSupportAgent":
            current_agent = requirements_agent
        elif result._last_agent.name == "RequirementsAgent":
            current_agent = code_agent

# 실행
await run_programming_support_agent()

이 코드는 사용자가 입력을 제공할 때마다 적절한 에이전트가 호출되며, 대화 흐름에 따라 에이전트 간 전환이 이루어집니다. 예를 들어, 사용자가 “웹사이트를 만들고 싶어요”라고 입력하면 메인 에이전트가 응답하고, 이어서 요구사항 수집 에이전트와 코드 생성 에이전트가 차례로 동작합니다.

그럼 이제 위 코드를 종합적으로 정리해서 실행해 보겠습니다. 주피터 노트북 환경에서 파이썬 3.13 버전으로 실행했습니다.

프로그래밍 AI 에이전트
프로그래밍 AI 에이전트

위와 같이 AI가 사용자에게 질문하고, 위 그림 맨 위에 사용자의 입력을 받기 위한 박스가 있습니다. 이 박스 부분에 입력하면 또 출력화면에 “사용자: 웹사이트를 만들고 싶어요”라고 사용자가 입력한 내용을 다시 보여주고, 이어서 질문을 합니다.

결국 질문의 의도가 파악되면 CodeGenerationAgent가 결과를 제시합니다. 위 결과 그림에서는 Flask 앱으로 구현하고 있습니다. 자세한 결과는 아래와 같습니다.

AI (CodeGenerationAgent): 로그인 기능이 있는 간단한 웹사이트 예제를 Python의 Flask 프레임워크를 사용하여 구현할 수 있습니다. 이 예제에서는 사용자 인증을 위해 사용자의 데이터를 하드코딩하고, 실제 데이터베이스 연동은 생략하겠습니다.

1. 먼저 Flask를 설치합니다:
   ```
   pip install Flask
   ```

2. 다음은 Flask 애플리케이션의 기본 구조입니다.

```python
from flask import Flask, render_template, request, redirect, url_for, flash, session

app = Flask(__name__)
app.secret_key = 'your_secret_key'

# 하드코딩된 사용자 데이터
users = {
    "user1": "password1",
    "user2": "password2"
}

@app.route('/')
def home():
    if 'username' in session:
        return f'Hello, {session["username"]}! <br><a href="/logout">Logout</a>'
    return 'You are not logged in. <br><a href="/login">Login</a>'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']

        if username in users and users[username] == password:
            session['username'] = username
            flash('Login successful!', 'success')
            return redirect(url_for('home'))
        else:
            flash('Invalid credentials, please try again.', 'danger')
    
    return render_template('login.html')

@app.route('/logout')
def logout():
    session.pop('username', None)
    flash('You have been logged out.', 'info')
    return redirect(url_for('home'))

if __name__ == '__main__':
    app.run(debug=True)
```

3. `templates` 폴더를 만들고, 그 안에 `login.html` 파일을 생성합니다.

```html
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
</head>
<body>
    <h2>Login</h2>
    {% with messages = get_flashed_messages(with_categories=true) %}
      {% if messages %}
        <ul>
        {% for category, message in messages %}
          <li class="{{ category }}">{{ message }}</li>
        {% endfor %}
        </ul>
      {% endif %}
    {% endwith %}
    <form method="post">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required><br>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required><br>
        <button type="submit">Login</button>
    </form>
</body>
</html>
```

이 코드는 사용자에게 로그인 양식을 제공하고 사용자가 성공적으로 로그인하면 세션에 사용자 이름을 저장합니다. 로그아웃하면 세션에서 사용자 정보를 제거합니다. `flask.flash`를 사용하여 메시지를 사용자에게 전달합니다.

실제로 사용하려면 데이터베이스를 사용해 사용자 정보를 관리하고 암호를 안전하게 해시 처리하는 것이 좋습니다.

지금까지 OpenAI API와 Agents SDK를 활용해 프로그래밍 AI 에이전트를 구현해 봤습니다. Agent SDK에서 제시하는 Runner의 경우 에이전트의 여러 상황에 유용하기에는 아직 부족한 부분이 많아 보였어요. 뭐 점점 좋아지겠죠?


답글 남기기