<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://synabreu.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://synabreu.github.io/" rel="alternate" type="text/html" /><updated>2026-08-21T14:34:13+09:00</updated><id>https://synabreu.github.io/feed.xml</id><title type="html">시나브로 AI 노트</title><subtitle>AI 테크 컬럼, 자습서, 소스코드</subtitle><author><name>{&quot;name&quot;=&gt;nil, &quot;avatar&quot;=&gt;nil, &quot;bio&quot;=&gt;&quot;서진호&quot;, &quot;location&quot;=&gt;&quot;Seoul. South Korea&quot;, &quot;email&quot;=&gt;&quot;synabreu@outlook.com&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Email&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-envelope-square&quot;, &quot;url&quot;=&gt;&quot;mailto:synabreu@outlook.com&quot;}, {&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://synabreu.github.io&quot;}, {&quot;label&quot;=&gt;&quot;Twitter&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-twitter-square&quot;}, {&quot;label&quot;=&gt;&quot;Facebook&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-facebook-square&quot;, &quot;url&quot;=&gt;&quot;https://www.facebook.com/jinho.seo.165&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/synabreu/&quot;}, {&quot;label&quot;=&gt;&quot;Instagram&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-instagram&quot;}]}</name><email>synabreu@outlook.com</email></author><entry><title type="html">OpenAI가 모델 개발 속도를 늦춘 이유</title><link href="https://synabreu.github.io/openai/OpenAI%EA%B0%80-%EB%AA%A8%EB%8D%B8-%EA%B0%9C%EB%B0%9C-%EC%86%8D%EB%8F%84%EB%A5%BC-%EB%8A%A6%EC%B6%98-%EC%9D%B4%EC%9C%A0/" rel="alternate" type="text/html" title="OpenAI가 모델 개발 속도를 늦춘 이유" /><published>2026-08-19T00:00:00+09:00</published><updated>2026-08-19T00:00:00+09:00</updated><id>https://synabreu.github.io/openai/OpenAI%EA%B0%80%20%EB%AA%A8%EB%8D%B8%20%EA%B0%9C%EB%B0%9C%20%EC%86%8D%EB%8F%84%EB%A5%BC%20%EB%8A%A6%EC%B6%98%20%EC%9D%B4%EC%9C%A0</id><content type="html" xml:base="https://synabreu.github.io/openai/OpenAI%EA%B0%80-%EB%AA%A8%EB%8D%B8-%EA%B0%9C%EB%B0%9C-%EC%86%8D%EB%8F%84%EB%A5%BC-%EB%8A%A6%EC%B6%98-%EC%9D%B4%EC%9C%A0/"><![CDATA[<p>OpenAI는 최근 몇 주 동안 두 가지 사건을 통해 고도화되는 AI 시스템의 위험이 빠르게 커지고 있음을 확인했다.</p>

<p>하나는 <strong>OpenAI-Hugging Face 사건</strong>이며, 다른 하나는 차기 모델인 <strong>Astra가 Preparedness Framework에서 정의한 Critical Cybersecurity Capability 임계 수준에 도달할 가능성이 있다는 초기 평가 결과</strong>다. 이러한 변화로 인해 OpenAI는 모델 학습 전 과정에서 모니터링, 정렬, 보안 격리를 강화할 필요성이 커졌다고 설명한다.</p>

<h1 id="1-모델-개발-속도를-일시적으로-늦추다">1. 모델 개발 속도를 일시적으로 늦추다</h1>

<p>모델 성능이 높아질수록 내부에서 모델을 개발하고 테스트하는 과정 자체의 위험도 커진다. OpenAI는 안전 기준을 충족하기 위해 최신 배포 예정 모델의 <strong>강화학습(RL)을 약 2주 동안 중단</strong>했다. 이 기간 연구 환경의 보안을 강화하고 레드팀 테스트를 수행했으며 모니터링 범위를 확대했다.</p>

<p>가장 큰 규모로 계획된 프런티어 강화 학습은 현재도 보류 상태다. 대신 소규모 학습과 평가를 통해 모델 행동, 안전장치, 정렬 수준을 먼저 확인하고 있다.</p>

<h1 id="2-세-가지-핵심-안전장치">2. 세 가지 핵심 안전장치</h1>

<p>OpenAI는 고성능 모델 개발에 세 가지 안전 계층을 적용하고 있다.</p>

<ul>
  <li><strong>Monitoring</strong>: 위험하거나 비정상적인 행동을 탐지하고 대응한다.</li>
  <li><strong>Alignment</strong>: 모델이 유해하거나 승인되지 않은 행동을 할 가능성을 낮춘다.</li>
  <li><strong>Security</strong>: 모델이 접근하거나 영향을 줄 수 있는 시스템 범위를 제한한다.</li>
</ul>

<p>OpenAI는 장기적으로 AI 모델이 다른 AI 모델을 방어하는 작업을 포함해 상당수의 보안 작업을 직접 수행하게 될 것으로 예상한다.</p>

<h1 id="3-연구-환경의-보안-강화">3. 연구 환경의 보안 강화</h1>

<p>프런티어 모델의 사이버 보안 능력이 향상되면서 학습과 평가 환경에 대한 보안 요구도 높아지고 있다. OpenAI는 다음 세 가지를 강화하고 있다.</p>

<ul>
  <li>
    <p><strong>Workload isolation</strong>: 모델이 생성한 코드나 신뢰할 수 없는 코드를 실행하는 작업을 더 강력한 샌드박스 환경에서 수행한다.</p>
  </li>
  <li>
    <p><strong>Network isolation</strong>: 위험도가 높은 워크로드가 인터넷이나 내부 네트워크에 자유롭게 접근하지 못하도록 제한한다.</p>
  </li>
  <li>
    <p><strong>Continuous security testing</strong>: 불필요한 공유 서비스를 제거하고 상시 권한을 줄이며, 로그 수집과 보안 경계 검증을 강화한다. AI 모델을 이용해 이러한 보안 경계를 지속적으로 공격 테스트하는 자동화도 진행하고 있다.</p>
  </li>
</ul>

<p>현재 애스트라(Astra)와 사이버 보안 관련 모델에는 가장 강한 수준의 보안 기준이 적용되고 있다.</p>

<h1 id="4-chain-of-thought-모니터링-확대">4. Chain-of-Thought 모니터링 확대</h1>

<p>OpenAI는 모델 내부 행동에 대한 모니터링도 크게 확대했다. 새로운 시스템은 여러 단계로 구성된다. 모델 내부 활동을 검사하는 <strong>activation classifier가 샘플링되는 각 토큰에서 동작</strong>하며, 문제가 의심되는 경우 더 많은 연산 자원을 사용하는 자동 조사 시스템으로 전달한다.</p>

<p>이 시스템은 모델의 도구 사용, 이용 가능한 사고 과정, 전체 행동 흐름을 분석해 다음과 같은 행동을 탐지한다.</p>

<ul>
  <li>승인되지 않은 시스템 접근</li>
  <li>데이터 탈취</li>
  <li>파괴적 행동</li>
  <li>안전장치 우회 시도</li>
</ul>

<p>위험 행동이 탐지되면 OpenAI는 약 <strong>30분 이내에 경고를 발생시키는 것</strong>을 목표로 하고 있다. 심각한 보안 경계 위반 가능성이 있을 경우 안전, 보안, 연구팀이 함께 검토하며 명확한 오탐으로 판단되지 않으면 해당 작업을 중단한다.</p>

<p>이 모니터링에는 상당한 연산 비용도 필요하다. OpenAI는 현재 모니터링 비용을 감시 대상 추론 연산량의 약 <strong>20% 수준</strong>으로 추정하고 있다.</p>

<h1 id="5-alignment-연구도-강화한다">5. Alignment 연구도 강화한다</h1>

<p>모델의 능력이 커질수록 단순한 보안 격리만으로는 충분하지 않다. 특히, 강화학습에서는 <strong>reward hacking</strong> 문제가 중요하다. 모델이 실제 목표를 달성하지 않고도 평가기, 보상 함수, 도구의 허점을 이용해 높은 보상을 받는 행동을 학습할 수 있기 때문이다.</p>

<p>OpenAI는 이를 줄이기 위해 보상 모델을 개선하고, 모델이 자신의 행동과 능력, 한계를 더 정직하게 표현하도록 학습하며, 평가 시스템이나 도구의 취약점을 악용하는 행동을 억제하고 있다.</p>

<h1 id="6-앞으로의-방향">6. 앞으로의 방향</h1>

<p>OpenAI는 기존 <strong>Preparedness Framework</strong>를 확장해 모델 자체의 능력뿐만 아니라 학습 환경과 배포 환경까지 함께 고려하는 방향으로 발전시킬 계획이다.</p>

<p><strong>프런티어 모델의 성능 향상 속도보다 모델을 이해하고 정렬하고 보호하는 기술의 발전 속도가 뒤처져서는 안 된다는 것이다.</strong> 개발자 관점에서도 의미가 크다. 앞으로 AI 시스템 개발에서는 모델 성능뿐 아니라 <strong>Sandbox, Network Isolation, Least Privilege, Agent Monitoring, Alignment Evaluation, Security Logging</strong> 등과 같은 요소가 더욱 중요해질 것이다.</p>

<p>AI 에이전트가 실제 코드, 네트워크, 클라우드 시스템과 연결되기 시작하면서 AI 안전성은 더 이상 모델 연구만의 문제가 아니다. 점점 <strong>시스템 아키텍처와 보안 엔지니어링의 문제</strong>가 되고 있다.</p>

<ul>
  <li><a href="https://openai.com/index/pacing-model-development-cyber-capabilities/">출처: OpenAI, <em>Pacing model development in an era of cyber-critical capabilities</em></a></li>
</ul>]]></content><author><name>{&quot;name&quot;=&gt;nil, &quot;avatar&quot;=&gt;nil, &quot;bio&quot;=&gt;&quot;서진호&quot;, &quot;location&quot;=&gt;&quot;Seoul. South Korea&quot;, &quot;email&quot;=&gt;&quot;synabreu@outlook.com&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Email&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-envelope-square&quot;, &quot;url&quot;=&gt;&quot;mailto:synabreu@outlook.com&quot;}, {&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://synabreu.github.io&quot;}, {&quot;label&quot;=&gt;&quot;Twitter&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-twitter-square&quot;}, {&quot;label&quot;=&gt;&quot;Facebook&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-facebook-square&quot;, &quot;url&quot;=&gt;&quot;https://www.facebook.com/jinho.seo.165&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/synabreu/&quot;}, {&quot;label&quot;=&gt;&quot;Instagram&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-instagram&quot;}]}</name><email>synabreu@outlook.com</email></author><category term="openai" /><category term="오픈AI" /><category term="OpenAI" /><category term="GPT-5.6" /><category term="agenticai" /><category term="aiagent" /><category term="Astra" /><category term="Huggingface" /><summary type="html"><![CDATA[OpenAI는 최근 몇 주 동안 두 가지 사건을 통해 고도화되는 AI 시스템의 위험이 빠르게 커지고 있음을 확인했다.]]></summary></entry><entry><title type="html">청소년을 위한 ChatGPT for Teens 출시</title><link href="https://synabreu.github.io/openai/%EC%B2%AD%EC%86%8C%EB%85%84%EC%9D%84-%EC%9C%84%ED%95%9C-ChatGPT-for-Teens-%EC%B6%9C%EC%8B%9C/" rel="alternate" type="text/html" title="청소년을 위한 ChatGPT for Teens 출시" /><published>2026-08-18T00:00:00+09:00</published><updated>2026-08-18T00:00:00+09:00</updated><id>https://synabreu.github.io/openai/%EC%B2%AD%EC%86%8C%EB%85%84%EC%9D%84%20%EC%9C%84%ED%95%9C%20ChatGPT%20for%20Teens%20%EC%B6%9C%EC%8B%9C</id><content type="html" xml:base="https://synabreu.github.io/openai/%EC%B2%AD%EC%86%8C%EB%85%84%EC%9D%84-%EC%9C%84%ED%95%9C-ChatGPT-for-Teens-%EC%B6%9C%EC%8B%9C/"><![CDATA[<p>ChatGPT for Teens가 주민등록번호나 사회보장번호가 필요없이 18세 미만 계정이 자동적으로 대상 적용되는 것이 서비스 흥미롭네요!</p>

<p>OpenAI가 오늘부터 ChatGPT for Teens를 전 세계의 대상 청소년 계정에 순차적으로 도입하기 시작했다. 핵심은 별도의 “청소년용 저성능 ChatGPT”를 만드는 것이 아니라, 기존 ChatGPT 능력을 유지하면서 18세 미만 사용자에게 학습 기능과 안전장치를 추가하는 것이다.</p>

<p>주요 내용을 정리하면 다음과 같다.</p>

<ul>
  <li>18세 미만 계정에 자동 적용된다. 계정에 등록된 나이, 연령 인증 또는 OpenAI의 연령 추정 시스템을 통해 18세 미만으로 판단되면 사용자가 별도로 켜지 않아도 ChatGPT for Teens가 활성화된다.</li>
  <li>학습 기능이 강화된다. Study Mode를 이용해 정답만 바로 제시하기보다 힌트, 단계별 설명, 후속 질문, 이해도 확인 등을 제공할 수 있다. 숙제와 관련된 질문에는 Study Mode 사용을 권할 수도 있으며 퀴즈 같은 학습 도구도 지원한다.</li>
  <li>스터디 시간을 설정할 수 있다. 청소년 본인 또는 연결된 부모·보호자가 특정 시간대에 새 대화를 자동으로 Study Mode에서 시작하도록 설정할 수 있다. 이것은 ChatGPT 사용 자체를 차단하는 기능과는 다르다.</li>
  <li>과도한 사용을 줄이기 위한 장치도 들어간다. 일정 상황에서 휴식을 권하는 메시지를 보여주고, ChatGPT가 사람이나 교사가 아니라 AI 도구라는 점을 다시 알려주는 알림이 제공될 수 있다. 이미지 업로드 전에는 개인정보나 민감한 정보가 포함돼 있지 않은지 확인하라는 안내도 표시될 수 있다.</li>
  <li>청소년에게 부적절하거나 위험할 수 있는 콘텐츠에 대한 보호 장치가 강화된다. 즉 일반 계정보다 연령에 맞춘 안전 정책이 추가 적용된다.</li>
</ul>

<p>그리고 부모 통제 기능에서 꽤 중요한 부분이 하나 있다. 부모나 보호자가 계정을 연결해 일부 설정, 스터디 시간, 사용 시간 제한 등을 관리할 수 있지만, 부모가 청소년의 ChatGPT 대화 내용을 직접 읽거나 감시할 수는 없다. 제한적인 안전 알림 상황에서도 OpenAI는 청소년의 안전을 지원하는 데 필요한 정보만 제공한다고 설명한다.</p>

<p>또한 사용 시간 제한을 사용하면 지정된 시간 동안 ChatGPT 접근 자체를 제한할 수 있다. 반면 공부 시간은 접근을 막는 것이 아니라 새 대화를 학습 중심 모드로 시작하도록 하는 기능이다. 18세가 되면 ChatGPT가 사용자를 성인으로 확인한 뒤 청소년 보호 설정을 해제하고 일반 ChatGPT 환경으로 전환할 수 있으며, 연결된 부모 통제 관계도 종료될 수 있다. 다만 일부 보호 기능은 이후 다시 설정할 수 있다.</p>

<p>특히 흥미로운 변화는 OpenAI가 청소년용 ChatGPT를 단순한 콘텐츠 필터링 제품으로 만들기보다, <code class="language-plaintext highlighter-rouge">Study Mode → Study Hours → Quiz → Break reminder → Parental Controls</code> 처럼 AI 튜터의 사용 방식 자체를 설계하려 한다는 점이다. 현재 기능은 지역별로 차이가 있으며, OpenAI는 향후 몇 주 동안 제공 범위를 확대할 예정이라고 밝혔다</p>]]></content><author><name>{&quot;name&quot;=&gt;nil, &quot;avatar&quot;=&gt;nil, &quot;bio&quot;=&gt;&quot;서진호&quot;, &quot;location&quot;=&gt;&quot;Seoul. South Korea&quot;, &quot;email&quot;=&gt;&quot;synabreu@outlook.com&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Email&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-envelope-square&quot;, &quot;url&quot;=&gt;&quot;mailto:synabreu@outlook.com&quot;}, {&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://synabreu.github.io&quot;}, {&quot;label&quot;=&gt;&quot;Twitter&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-twitter-square&quot;}, {&quot;label&quot;=&gt;&quot;Facebook&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-facebook-square&quot;, &quot;url&quot;=&gt;&quot;https://www.facebook.com/jinho.seo.165&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/synabreu/&quot;}, {&quot;label&quot;=&gt;&quot;Instagram&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-instagram&quot;}]}</name><email>synabreu@outlook.com</email></author><category term="openai" /><category term="오픈AI" /><category term="OpenAI" /><category term="GPT-5.6" /><category term="agenticai" /><category term="aiagent" /><category term="ChatGPT for Teens" /><summary type="html"><![CDATA[ChatGPT for Teens가 주민등록번호나 사회보장번호가 필요없이 18세 미만 계정이 자동적으로 대상 적용되는 것이 서비스 흥미롭네요!]]></summary></entry><entry><title type="html">LLM 서빙 베스트 사례</title><link href="https://synabreu.github.io/opensource/LLM-%EC%84%9C%EB%B9%99-%EB%B2%A0%EC%8A%A4%ED%8A%B8-%EC%82%AC%EB%A1%80/" rel="alternate" type="text/html" title="LLM 서빙 베스트 사례" /><published>2026-08-17T00:00:00+09:00</published><updated>2026-08-17T00:00:00+09:00</updated><id>https://synabreu.github.io/opensource/LLM%20%EC%84%9C%EB%B9%99%20%EB%B2%A0%EC%8A%A4%ED%8A%B8%20%EC%82%AC%EB%A1%80</id><content type="html" xml:base="https://synabreu.github.io/opensource/LLM-%EC%84%9C%EB%B9%99-%EB%B2%A0%EC%8A%A4%ED%8A%B8-%EC%82%AC%EB%A1%80/"><![CDATA[<p>제 4 장의 내용을 읽고 아래와 같은 질문과 답변을 달았다. 이 장을 하나의 흐름으로 정리하면 이렇게 된다. 따라서 이 챕터의 가장 중요한 메시지는 세 문장으로 압축할 수 있다.</p>

<p>첫째, Agentic AI 시대에는 모델 서빙의 최적화 단위가 “한 번의 LLM 추론”이 아니라 “전체 Agent control loop”가 된다.</p>

<p>둘째, 프로덕션 LLM 플랫폼은 단순 GPU inference server가 아니라 <code class="language-plaintext highlighter-rouge">API → orchestration → resource management → distributed serving → inference → optimization</code>으로 분리된 계층형 시스템이어야 한다.</p>

<p>셋째, Bedrock과 직접 구축 중 어느 것이 더 좋은지는 고정된 답이 없으며, E2E/TTFT/ITL과 RPS/TPS, 비용, 트래픽 패턴을 측정하면서 필요에 따라 스펙트럼상의 위치를 이동하는 것이 가장 현실적인 전략이다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
             Agentic Application
                     │
        한 요청에서 여러 호출 발생
                     │
                     ▼
       Serving Complexity 증가
                     │
                     ▼
     Enterprise Layered Architecture
                     │
          ┌──────────┴───────────┐
          │                      │
     Cloud Managed            Build
       Bedrock            K8s/Ray/vLLM
          │                      │
          └──────────┬───────────┘
                     │
            어느 쪽이 더 좋은가?
                     │
                     ▼
             Performance 측정
                     │
          ┌──────────┴─────────┐
          │                    │
      Latency              Throughput
 E2E / TTFT / ITL         RPS / TPS
          │                    │
          └──────────┬─────────┘
                     │
                     ▼
            Cost / SLO 판단
                     │
                     ▼
          Build ↔ Cloud 위치 조정
</code></pre></div></div>

<h1 id="1-에이전트-agentic-워크플로우가-등장하면서-모델-서빙-요구사항은-어떻게-달라지는가">1. 에이전트 Agentic 워크플로우가 등장하면서 모델 서빙 요구사항은 어떻게 달라지는가?</h1>

<p><strong>모델 서빙의 단위가 “한 번의 추론”에서 “하나의 작업을 끝내기
위한 반복적인 제어 루프”로 바뀐다는 것</strong>이다. 전통적인 LLM 서비스는 대체로 다음과 같다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>User
↓
LLM
↓
Response
</code></pre></div></div>

<p>요청 한 번에 모델을 한 번 호출하고 응답하면 끝난다. 에이전틱 시스템에서는 구조가 달라집니다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>User
↓
Agent
↓
Planning ──→ LLM
↓
Retrieval ─→ Embedding / Vector DB
↓
Reasoning ─→ LLM
↓
Tool Call ─→ API / DB / Search
↓
Result
↓
LLM
↓
필요하면 다시 반복
↓
Final Answer
</code></pre></div></div>

<p>즉, &gt; <strong>1 User Request ≠ 1 Model Request</strong>가 된다. Knowledge Agent 실습이 좋은 예이다. 사용자가 단 한 번 질문했지만 내부적으로는</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Planner LLM      1회
Query Embedding  1회
Summary LLM      1회
Analysis LLM     1회
----------------------
총 API 호출 4회
</code></pre></div></div>

<p>가 발생했다.</p>

<h2 id="1-1-서빙-시스템에-생기는-5가지-변화">1-1. 서빙 시스템에 생기는 5가지 변화</h2>

<ul>
  <li>
    <p><strong>토큰 사용량 증가</strong>: Planner, RAG context, tool result,
intermediate result가 다음 LLM 호출에 계속 들어가므로 요청당 토큰
소비량이 급격히 증가한다.</p>
  </li>
  <li>
    <p><strong>지연 시간 증폭(Latency amplification)</strong>: Agent workflow가 직렬로 연결되어 있다면,</p>
  </li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Total Agent Latency
≈ L1 + L2 + L3 + ... + Ln
</code></pre></div></div>

<p>처럼 각 단계의 E2E latency가 누적된다.
예를 들어,</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Planning       0.7s
RAG            0.3s
Summary        1.4s
Analysis       2.0s
------------------
Agent E2E      4.4s
</code></pre></div></div>

<p>가 될 수 있다. 특히 다음 단계가 이전 단계의 <strong>전체 결과</strong>를 필요로 한다면 TTFT가 아무리 빨라도 전체 작업은 빨라지지 않는다.</p>

<ul>
  <li>
    <p><strong>동적인 트래픽 패턴</strong>: 단순 질문은 LLM 한 번으로 끝날 수도
있지만 복잡한 질문은 5회, 10회, 20회 호출할 수도 있다. 따라서, <code class="language-plaintext highlighter-rouge">User RPS = 10</code> 이라고 해서 <code class="language-plaintext highlighter-rouge">Model RPS = 10</code>인 것이 아니다. 평균적으로 한 요청이 LLM을 6번 호출한다면 실질적으로는 <code class="language-plaintext highlighter-rouge">10 user RPS × 6 calls ≈ 60 model RPS</code>가 된다.</p>
  </li>
  <li>
    <p>여러 모델을 동시에 서빙해야 하는 문제: 현대 에이전트는 보통 하나의 모델만 쓰지 않는다.</p>
  </li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Planner    → Small/Reasoning LLM
Generation → Large LLM
Embedding → Embedding Model
Vision → VLM
Speech → ASR/TTS
Reranker → Ranking Model
</code></pre></div></div>

<p>그래서 모델 선택과 오케스트레이션(orchestration)이 코어 추론(Core Inference)만큼 중요해진다.</p>

<ul>
  <li><strong>상태와 메모리 관리</strong>: 이전 호출의 결과, tool result,
conversation history, RAG context, KV cache 등을 다음 호출에서
재사용해야 한다. 그래서 에이전틱 서빙(Agentic Serving)에서는 다음 기술의 중요성이 크게 올라간다.</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>KV Cache
Prompt Cache
CAG
Cache-aware Routing
Continuous Batching
Scheduler
Model Routing
Tool Orchestration
Observability
</code></pre></div></div>

<h2 id="1-2-rag와-cag의-의미도-달라진다">1-2. RAG와 CAG의 의미도 달라진다</h2>

<p>RAG는 매 요청마다 외부 지식(nowledge)을 검색한다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Query
    → Embedding
    → Vector Search
    → Retrieved Context
    → LLM
</code></pre></div></div>

<p>장점은 최신성과 동적 지식이다. 반면 문맥 증강 생성(CAG,Context-Augmented Generation)는 자주 재사용하는 지식을 미리 문맥과 KV 캐시에 올려 놓는다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Knowledge
    → Prefill once
    → KV Cache
        ↓
Query → reuse
</code></pre></div></div>

<p>따라서 반복적인 에이전틱 워크로드에서는 RAG와 CAG를 경쟁 관계라기보다</p>

<blockquote>
  <p><strong>RAG = 지식 검색 최적화</strong> <strong>CAG = 반복 추론 실행 최적화</strong></p>
</blockquote>

<p>로 볼 수 있다.</p>

<h1 id="2-엔터프라이즈-llm-서빙-플랫폼의-계층형-아키텍처는-어떻게-구성되며-kubernetesray-serve에서는-어떻게-구현하는가">2. 엔터프라이즈 LLM 서빙 플랫폼의 계층형 아키텍처는 어떻게 구성되며 Kubernetes/Ray Serve에서는 어떻게 구현하는가?</h1>

<p>엔터프라이즈급 플랫폼에서 중요한 생각은</p>

<blockquote>
  <p><strong>“LLM을 GPU에서 실행하는 것”과</strong> <strong>“LLM 서비스를 운영하는 것”은
완전히 다른 문제다.</strong></p>
</blockquote>

<p>전체 구조를 단순화하면 다음과 같다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>             Client / Agent
                    │
                    ▼
┌──────────────────────────────────┐
│ 1. Public API                    │
│ Auth / Rate Limit / Billing      │
└──────────────────────────────────┘
                    │
                    ▼
┌──────────────────────────────────┐
│ 2. Model Selection              │
│ Routing / Cost / Quality / SLA   │
└──────────────────────────────────┘
                    │
                    ▼
┌──────────────────────────────────┐
│ 3. Resource Management          │
│ GPU / CPU / Memory / Scheduling  │
└──────────────────────────────────┘
                    │
                    ▼
┌──────────────────────────────────┐
│ 4. Distributed Serving          │
│ Multi-GPU / Cache / Multi-node   │
└──────────────────────────────────┘
                    │
                    ▼
┌──────────────────────────────────┐
│ 5. Core Inference               │
│ vLLM / TRT-LLM / SGLang          │
└──────────────────────────────────┘
                    │
                    ▼
┌──────────────────────────────────┐
│ 6. Model Optimization           │
│ Quant / Spec Decode / Kernels    │
└──────────────────────────────────┘
                    │
                    ▼
┌──────────────────────────────────┐
│ 7. Models                        │
│ Base / FT / Adapter / Version    │
└──────────────────────────────────┘
</code></pre></div></div>

<h2 id="21-layer-1---공인-api">2.1 Layer 1 - 공인 API</h2>

<p>역할은 외부 트래픽을 안전하게 받아들이는 것이다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Authentication
Authorization
Tenant
Quota
Rate Limit
Billing
Routing
DDoS protection
</code></pre></div></div>

<p>Open Source에서는 보통</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FastAPI
+
Kubernetes Service
+
Ingress / Gateway
+
Redis
+
JWT / API Key
</code></pre></div></div>

<p>등으로 구현할 수 있다. 예를 들어 두 단계의 요청 제한(rate limiting)이 가능하다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ingress
→ global rate limit

FastAPI
→ tenant-specific quota
</code></pre></div></div>

<p>이는 심층 방어(defense-in-depth) 구조이다.</p>

<h2 id="2-2-layer-2---모델-선택과-오케스트레이션">2-2. Layer 2 - 모델 선택과 오케스트레이션</h2>

<p>모든 요청에 가장 큰 모델을 쓰는 것은 비효율적이다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Simple Request
↓
Small Model

Complex Reasoning
↓
Large Model
</code></pre></div></div>

<p>이 레이어에서 최적화하는 품질(Quality), 지연시간(latency) 비용 등 세 가지가 있다. 또한 모델 라우팅(Model routing), 카나리 배포(Canary deployment), 테넌트 지정 모델(Tenant-specific model), 추측 디코딩(Speculative decoding), 대체 경로(Fallback), 로드 밸런싱(Load Balancing) 등도 수행한다.</p>

<p>예를 들어:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">if</span> <span class="n">long_generation</span><span class="p">:</span>
    <span class="n">speculative_decode</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
    <span class="n">normal_model</span><span class="p">()</span>
</code></pre></div></div>

<p>처럼 모델 실행 전략 자체를 바꿀 수도 있다.</p>

<h2 id="2-3-layer-3---리소스-관리">2-3. Layer 3 - 리소스 관리</h2>

<p>레이어3는 모델보다 <strong>GPU라는 희소 자원</strong>이 중심이다. 
질문은 이런 것이다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>누가 GPU를 사용할 것인가?
몇 GPU를 할당할 것인가?
H100과 L40S 중 무엇을 사용할 것인가?
GPU utilization은 얼마인가?
Priority workload는 무엇인가?
</code></pre></div></div>

<p>Kubernetes에서는 이를 포드(Pod), 스케줄러, GPU 오퍼레이터, 디바이스 플러그인(Device Plugin), 노드 셀렉터(Node Selector), 노드 배제 설정과 파드 허용 규칙(Taint/Toleration), HPA, 큐(Kueue), 카펜터(Karpenter) 등으로 관리할 수 있다.</p>

<p>Ray에서는</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ray_actor_options</span><span class="o">=</span><span class="p">{</span>
    <span class="s">"num_gpus"</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">}</span>
</code></pre></div></div>

<p>같은 식으로 애플리케이션이 필요한 리소스를 선언할 수 있다.</p>

<h2 id="24-layer-4---분산-서빙">2.4 Layer 4 - 분산 서빙</h2>

<p>모델이 하나의 GPU에 들어가지 않을 때 필요하다. 예를 들어, <code class="language-plaintext highlighter-rouge">Model Weight = 160 GB, GPU VRAM = 80 GB</code> 라면 최소 2 GPU가 필요합니다. 대표 기술은</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Tensor Parallel
Pipeline Parallel
Data Parallel
Prefill/Decode Disaggregation
Distributed KV Cache
Prompt Cache
Cache-aware Routing
</code></pre></div></div>

<p>이다. 예를 들어,</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Node 1: GPU × 8 → TP=8
Node 2: GPU × 8 → TP=8
Inter-Node (노드 간) → PP=2
</code></pre></div></div>

<p>처럼 구성할 수 있다.</p>

<h2 id="24-layer-5---코어-추론core-inference">2.4 Layer 5 - 코어 추론(Core Inference)</h2>

<p>여기가 실제 토큰 계산이 일어나는 부분이다. 대표 추론 엔진은 <code class="language-plaintext highlighter-rouge">vLLM, TensorRT-LLM, SGLang, Triton</code> 등이다. 그리고 아래쪽에는</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FlashAttention
PagedAttention
GEMM
CUDA kernels
</code></pre></div></div>

<p>같은 최적화가 존재한다.
구조적으로는</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ray Serve
↓
vLLM
↓
PyTorch / CUDA
↓
GPU
</code></pre></div></div>

<p>이다. Ray Serve와 vLLM의 역할을 혼동하면 안 됩된다.</p>

<blockquote>
  <p><strong>Ray Serve = 서비스 오케스트레이션 / 스케일링</strong> <strong>vLLM = LLM 추론 엔진</strong></p>
</blockquote>

<p>이다.</p>

<h2 id="25-layer-6---모델-최적화model-optimization">2.5 Layer 6 - 모델 최적화(Model Optimization)</h2>

<p>모델 최적화는 모델을 새로 학습하지 않고 추론 효율을 높인다.</p>

<p>대표 기술로는 다음과 같다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FP8
INT8
INT4
AWQ
GPTQ
Speculative Decoding
KV Cache optimization
Kernel fusion
Continuous batching
</code></pre></div></div>

<p>모든 최적화가 같은 지표를 좋아지게 하는 것은 아니다. 예를 들어 처리량(throughput)을 극대화하기 위해 배치를 크게 하면 TTFT는 악화될 수 있다.</p>

<h2 id="26-kubernetes--kuberay--ray-serve--vllm로-합치면">2.6 Kubernetes + KubeRay + Ray Serve + vLLM로 합치면</h2>

<p>제 4 장 오픈소스 구현을 가장 간결하게 나타내면:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                Internet
                     │
                Ingress/Gateway
                     │
                  FastAPI
                     │
               Model Router
                     │
               Ray Serve
                     │
          ┌──────────┴─────────┐
          │                    │
     LLMServer             LLMServer
          │                    │
        vLLM                  vLLM
          │                    │
        GPU 0                 GPU 1

────────────────────────────────────

Kubernetes
├─ Networking
├─ Pod lifecycle
├─ GPU allocation
├─ Scaling
└─ Monitoring

KubeRay
└─ Ray lifecycle

Ray Serve
├─ Deployment
├─ Replica
├─ Routing
├─ Autoscaling
└─ Multiplexing

vLLM
├─ PagedAttention
├─ KV cache
├─ Continuous batching
└─ Token generation
</code></pre></div></div>

<p>특히 프로덕션 서빙에서는 <code class="language-plaintext highlighter-rouge">RayCluster</code>보다 <code class="language-plaintext highlighter-rouge">RayService</code>가 자연스럽다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RayCluster = Ray Computing Cluster Management
RayService = RayCluster
    + Ray Serve Application
    + Health Check
    + Rolling Update
    + HA
</code></pre></div></div>

<p>그래서 KubeRay 기반 LLM serving의 전형적인 구조는</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Kubernetes
↓
KubeRay Operator
↓
RayService
↓
Ray Serve
↓
vLLM
↓
GPU
</code></pre></div></div>

<p>가 된다.</p>

<h1 id="3-build-vs-cloud는-어떤-기준으로-선택하며-무엇을-측정해야-하는가">3. Build vs Cloud는 어떤 기준으로 선택하며, 무엇을 측정해야 하는가?</h1>

<p>가장 중요한 결론부터 말하면:</p>

<blockquote>
  <p><strong>Build vs Buy가 아니라 Control Spectrum이다.</strong></p>
</blockquote>

<p>즉 다음 두 극단 사이에서 움직이는 문제이다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    Managed                                      DIY

Bedrock
↓
JumpStart
↓
BYO Model
↓
BYO Code
↓
BYO Container
↓
EKS + Ray Serve + vLLM
</code></pre></div></div>

<p>왼쪽으로 갈수록</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ease of Use ↑
Operation burden ↓
Control ↓
</code></pre></div></div>

<p>오른쪽으로 갈수록</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Control ↑
Optimization freedom ↑
Operation burden ↑
</code></pre></div></div>
<p>이다.</p>

<h2 id="3-1-aws-6단계로-보면">3-1. AWS 6단계로 보면</h2>

<table>
  <thead>
    <tr>
      <th>단계</th>
      <th>방식</th>
      <th style="text-align: right">편의성</th>
      <th style="text-align: right">통제권</th>
      <th style="text-align: right">운영 부담</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>Bedrock</td>
      <td style="text-align: right">매우 높음</td>
      <td style="text-align: right">매우 낮음</td>
      <td style="text-align: right">매우 낮음</td>
    </tr>
    <tr>
      <td>2</td>
      <td>JumpStart</td>
      <td style="text-align: right">높음</td>
      <td style="text-align: right">낮음</td>
      <td style="text-align: right">낮음</td>
    </tr>
    <tr>
      <td>3</td>
      <td>BYO Model</td>
      <td style="text-align: right">중상</td>
      <td style="text-align: right">중간</td>
      <td style="text-align: right">중간</td>
    </tr>
    <tr>
      <td>4</td>
      <td>BYO Code</td>
      <td style="text-align: right">중간</td>
      <td style="text-align: right">높음</td>
      <td style="text-align: right">중상</td>
    </tr>
    <tr>
      <td>5</td>
      <td>BYO Image</td>
      <td style="text-align: right">낮음</td>
      <td style="text-align: right">매우 높음</td>
      <td style="text-align: right">높음</td>
    </tr>
    <tr>
      <td>6</td>
      <td>BYO Infrastructure</td>
      <td style="text-align: right">가장 낮음</td>
      <td style="text-align: right">최고</td>
      <td style="text-align: right">최고</td>
    </tr>
  </tbody>
</table>

<h2 id="31-선택-기준-1---시장-출시time-to-market">3.1 선택 기준 1 - 시장 출시(Time to Market)</h2>

<p>PoC 단계라면 <code class="language-plaintext highlighter-rouge">Bedrock</code> 같은 관리형 API가 매우 유리한다. 모델 서빙 플랫폼 구축에 몇 주를 쓰는 대신 바로 애플리케이션 검증이 가능하다.</p>

<h2 id="32-선택-기준-2---맞춤화customization">3.2 선택 기준 2 - 맞춤화(Customization)</h2>

<p>다음과 같은 요구가 생기면 오른쪽으로 이동한다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Custom batching
Custom routing
Quantization
Speculative decoding
Custom CUDA kernel
Custom vLLM version
GPU sharing
KV cache routing
Prefill/decode separation
</code></pre></div></div>

<p>이런 요구는 관리형 API에서 구현하기 어렵다.</p>

<h2 id="32-선택-기준-3---트래픽-볼륨traffic-volume">3.2 선택 기준 3 - 트래픽 볼륨(Traffic Volume)</h2>

<p>트래픽이 작거나 불규칙하면 서버리스가 유리하다.</p>

<p><code class="language-plaintext highlighter-rouge">Low / burst traffic → Bedrock</code></p>

<p>반대로 항상 높은 부하가 유지된다면</p>

<p><code class="language-plaintext highlighter-rouge">High sustained traffic → Dedicated GPU</code></p>

<p>가 비용 면에서 유리할 수 있다.
따라서 중요한 질문은</p>

<blockquote>
  <p><strong>“얼마나 많은 요청이 들어오느냐?”보다</strong> <strong>“얼마나 많은 토큰을 얼마나 지속적으로 처리하느냐?”</strong></p>
</blockquote>

<p>이다.</p>

<h2 id="33-선택-기준-4---비용cost">3.3 선택 기준 4 - 비용(Cost)</h2>

<p>관리형 API는 보통 <code class="language-plaintext highlighter-rouge">$/token</code>이고 직접 호스팅은 <code class="language-plaintext highlighter-rouge">$/GPU-hour</code> 이다. 따라서 어느 지점에서는 손익분기점이 생긴다. 개념적으로 <code class="language-plaintext highlighter-rouge">API cost / sec = Tokens/sec × Price/token</code> 과 <code class="language-plaintext highlighter-rouge">GPU cost / sec</code>를 비교하면 된다.</p>

<p>트래픽이 충분히 크고 일정하면 전용 GPU가 유리해질 가능성이 커진다. 반면 utilization이 5~10% 수준이라면 비싼 GPU를 24시간 유지하는 것보다 관리형 API가 더 저렴할 수 있다.</p>

<p>그래서 반드시 성능을 측정해야 한다. Build vs Cloud 판단은 느낌으로 하는 것이 아니라 <strong>SLO + 비용 + 성능 지표</strong>로 해야 한다. 가장 중요한 지표는 크게 지연시간과 처리량 두 종류이다.</p>

<h1 id="4-지연시간latency">4 지연시간(Latency)</h1>

<h2 id="41-e2e-latency">4.1 E2E Latency</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Request
│
├─ Queue
├─ Routing
├─ Prefill
├─ Decode
├─ Network
↓
Response Complete
</code></pre></div></div>

<p>전체 시간이다. 에이전틱 워크플로에서는 중요하다. 왜냐하면,</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Agent E2E ≈ Step1 E2E
    + Step2 E2E
    + Step3 E2E
    + ...
</code></pre></div></div>

<p>가 되기 때문이다.</p>

<h2 id="42-ttft---time-to-first-token">4.2 TTFT - Time To First Token</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Request
↓
Prefill
↓
First Token
</code></pre></div></div>

<p>첫 토큰이 나올 때까지의 시간이다. 사용자가 느끼는 **“응답이 시작되는 속도”**이다. 그래서 Chat UI에서는 매우 중요하다.</p>

<h2 id="43-itl--tpot">4.3 ITL / TPOT</h2>

<p>첫 토큰 이후 토큰 사이의 간격이다. <code class="language-plaintext highlighter-rouge">Token1 ──40ms── Token2 ──40ms── Token3</code> 라면 `ITL ≈ 40 ms’ 이다. 출력이 길수록 중요하다.</p>

<h2 id="44-이들의-관계">4.4 이들의 관계</h2>

<p>출력 토큰이 <code class="language-plaintext highlighter-rouge">N</code>이고 ITL이 일정하다고 단순화하면:</p>

<p><code class="language-plaintext highlighter-rouge">E2E ≈ TTFT + ITL × (N - 1)</code></p>

<p>예:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TTFT = 0.5 sec
ITL = 0.04 sec
N = 101
E2E
= 0.5 + 0.04 × 100
= 4.5 sec
</code></pre></div></div>
<p>이다.</p>

<h1 id="5-처리량throughput">5. 처리량(Throughput)</h1>

<h2 id="51-rps">5.1 RPS</h2>

<p><code class="language-plaintext highlighter-rouge">Requests / Second</code></p>

<p>예: <code class="language-plaintext highlighter-rouge">1177 requests / 300 sec ≈ 3.92 RPS</code></p>

<p>앞서 Ray Serve 실습 결과와 같다. 하지만 LLM에서는 RPS만 보면 위험하다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Request A
10 input → 10 output

Request B
8000 input → 2000 output
</code></pre></div></div>

<p>둘을 똑같이 “1 request”로 계산하기 때문이다.</p>

<h2 id="5-2-tps">5-2. TPS</h2>

<p><code class="language-plaintext highlighter-rouge">Generated Tokens / Second</code></p>

<p>LLM에서는 RPS보다 모델 처리 능력을 더 잘 보여준다. 하지만 이것도 조건을 명시해야 한다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Input length
Output length
Concurrency
Batch size
Model
Precision
GPU
Context length
</code></pre></div></div>

<p>가 달라지면 TPS가 크게 달라진다. 그래서</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Model A = 10,000 TPS
Model B = 8,000 TPS
</code></pre></div></div>

<p>라는 숫자만으로 A가 더 빠르다고 말해서는 안 된다.</p>]]></content><author><name>{&quot;name&quot;=&gt;nil, &quot;avatar&quot;=&gt;nil, &quot;bio&quot;=&gt;&quot;서진호&quot;, &quot;location&quot;=&gt;&quot;Seoul. South Korea&quot;, &quot;email&quot;=&gt;&quot;synabreu@outlook.com&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Email&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-envelope-square&quot;, &quot;url&quot;=&gt;&quot;mailto:synabreu@outlook.com&quot;}, {&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://synabreu.github.io&quot;}, {&quot;label&quot;=&gt;&quot;Twitter&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-twitter-square&quot;}, {&quot;label&quot;=&gt;&quot;Facebook&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-facebook-square&quot;, &quot;url&quot;=&gt;&quot;https://www.facebook.com/jinho.seo.165&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/synabreu/&quot;}, {&quot;label&quot;=&gt;&quot;Instagram&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-instagram&quot;}]}</name><email>synabreu@outlook.com</email></author><category term="opensource" /><category term="오픈AI" /><category term="OpenAI" /><category term="GPT-5.6" /><category term="agenticai" /><category term="aiagent" /><category term="ChatGPT" /><category term="inference" /><category term="deployment" /><summary type="html"><![CDATA[제 4 장의 내용을 읽고 아래와 같은 질문과 답변을 달았다. 이 장을 하나의 흐름으로 정리하면 이렇게 된다. 따라서 이 챕터의 가장 중요한 메시지는 세 문장으로 압축할 수 있다.]]></summary></entry><entry><title type="html">[실습] OpenAI 에이전트 도커 워크삽 (1)-전체목차</title><link href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(1)-%EC%A0%84%EC%B2%B4-%EB%AA%A9%EC%B0%A8/" rel="alternate" type="text/html" title="[실습] OpenAI 에이전트 도커 워크삽 (1)-전체목차" /><published>2026-08-16T00:00:00+09:00</published><updated>2026-08-16T00:00:00+09:00</updated><id>https://synabreu.github.io/openai/%5B%EC%8B%A4%EC%8A%B5%5D%20OpenAI%20%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%20%EB%8F%84%EC%BB%A4%20%EC%9B%8C%ED%81%AC%EC%82%BD%20(1)-%EC%A0%84%EC%B2%B4%20%EB%AA%A9%EC%B0%A8</id><content type="html" xml:base="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(1)-%EC%A0%84%EC%B2%B4-%EB%AA%A9%EC%B0%A8/"><![CDATA[<p>윈도우11 운영체제 환경에서 Powershell을 포함한 Visual Studio Code를 이용해 OpenAI Agents SDK 앱을 개발하고, 최종 결과물을 리눅스 도커 이미지(Linux Docker image)로 빌드하여 Windows용 Docker Desktop, Public Cloud, On-Premises Linux/Kubernetes에서 재사용하는 전체 흐름을 익히는 핸즈온 워크삽이다.</p>

<blockquote>
  <p>중요: 이 프로젝트는 WSL Ubuntu 터미널을 사용하지 않는다. 개발 작업은 Windows 파일 시스템과 파워셀(PowerShell)에서 수행한다. Docker Desktop의 내부 리눅스 컨테이너 실행 방식은 사용자의 Docker Desktop 설정에 따라 달라질 수 있다.</p>
</blockquote>

<h1 id="1-학습목표">1. 학습목표</h1>

<p>전체 워크삽의 학습 목표를 완료하면 다음을 할 수 있다.</p>

<ol>
  <li>Windows Native Python 가상환경에서 Agents SDK 앱을 실행한다.</li>
  <li><code class="language-plaintext highlighter-rouge">Agent</code>와 <code class="language-plaintext highlighter-rouge">Runner</code>의 역할을 설명한다.</li>
  <li>Python 함수를 <code class="language-plaintext highlighter-rouge">function_tool</code>로 Agent Tool에 연결한다.</li>
  <li>Handoff를 이용해 사용자 요청을 전문 Agent로 전달한다.</li>
  <li>Agents SDK 앱을 FastAPI REST API로 감싼다.</li>
  <li>Windows에서 Linux Docker image를 빌드한다.</li>
  <li>API Key를 Docker image에 넣지 않고 런타임에 전달한다.</li>
  <li>동일 이미지를 Public Cloud와 On-Premises로 이동하는 원리를 설명한다.</li>
</ol>

<h1 id="2-전체-목차">2. 전체 목차</h1>

<ul>
  <li><a href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(2)-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95/">[실습] OpenAI 에이전트 도커 워크삽 (2)-환경설정</a></li>
  <li><a href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(3)-agents-%EB%B6%84%EC%84%9D/">[실습] OpenAI 에이전트 도커 워크삽 (3)-agents 분석</a></li>
  <li><a href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(4)-main-%EC%86%8C%EC%8A%A4%EB%B6%84%EC%84%9D/">[실습] OpenAI 에이전트 도커 워크삽 (4)-main 소스분석</a></li>
  <li><a href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(5)-FastAPI-%EC%84%9C%EB%B9%84%EC%8A%A4/">[실습] OpenAI 에이전트 도커 워크삽 (5)-FastAPI 서비스</a></li>
  <li><a href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(6)-Dockerfile-%EA%B3%BC-docker-compose/">[실습] OpenAI 에이전트 도커 워크삽 (6)-Dockerfile 과 docker-compose</a></li>
  <li><a href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(7)-%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B9%8C%EB%93%9C/">[실습] OpenAI 에이전트 도커 워크삽 (7)-리눅스 이미지 빌드</a></li>
  <li><a href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(8)-%EB%8F%84%EC%BB%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%8B%A4%ED%96%89/">[실습] OpenAI 에이전트 도커 워크삽 (8)-도커 컨테이너</a></li>
</ul>]]></content><author><name>{&quot;name&quot;=&gt;nil, &quot;avatar&quot;=&gt;nil, &quot;bio&quot;=&gt;&quot;서진호&quot;, &quot;location&quot;=&gt;&quot;Seoul. South Korea&quot;, &quot;email&quot;=&gt;&quot;synabreu@outlook.com&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Email&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-envelope-square&quot;, &quot;url&quot;=&gt;&quot;mailto:synabreu@outlook.com&quot;}, {&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://synabreu.github.io&quot;}, {&quot;label&quot;=&gt;&quot;Twitter&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-twitter-square&quot;}, {&quot;label&quot;=&gt;&quot;Facebook&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-facebook-square&quot;, &quot;url&quot;=&gt;&quot;https://www.facebook.com/jinho.seo.165&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/synabreu/&quot;}, {&quot;label&quot;=&gt;&quot;Instagram&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-instagram&quot;}]}</name><email>synabreu@outlook.com</email></author><category term="openai" /><category term="오픈AI" /><category term="OpenAI" /><category term="GPT-5.6" /><category term="agenticai" /><category term="aiagent" /><category term="openrouter" /><category term="docker" /><category term="powershell" /><category term="fastapi" /><category term="swagger-ui" /><category term="visual studio code" /><category term="Linux" /><category term="Windows11" /><category term="Kubernetes" /><category term="On-Premises" /><summary type="html"><![CDATA[윈도우11 운영체제 환경에서 Powershell을 포함한 Visual Studio Code를 이용해 OpenAI Agents SDK 앱을 개발하고, 최종 결과물을 리눅스 도커 이미지(Linux Docker image)로 빌드하여 Windows용 Docker Desktop, Public Cloud, On-Premises Linux/Kubernetes에서 재사용하는 전체 흐름을 익히는 핸즈온 워크삽이다.]]></summary></entry><entry><title type="html">[실습] OpenAI 에이전트 도커 워크삽 (10)-Docker Hub</title><link href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(10)-Docker-Hub/" rel="alternate" type="text/html" title="[실습] OpenAI 에이전트 도커 워크삽 (10)-Docker Hub" /><published>2026-08-16T00:00:00+09:00</published><updated>2026-08-16T00:00:00+09:00</updated><id>https://synabreu.github.io/openai/%5B%EC%8B%A4%EC%8A%B5%5D%20OpenAI%20%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%20%EB%8F%84%EC%BB%A4%20%EC%9B%8C%ED%81%AC%EC%82%BD%20(10)-Docker%20Hub</id><content type="html" xml:base="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(10)-Docker-Hub/"><![CDATA[<p>이 실습은 도커 허브(Docker Hub) 또는 사설 레지스트리(Private Registery) 로 이동하는 방법이다. 참고로 도커 허브란 도커 이미지를 저장하고 공유하는 클라우드 저장소이다. 또한, 사설 레지스트리는 기업 조직 내부에서만 사용하는 비공개 도커 이미지 저장소를 말한다.</p>

<h1 id="1-실습-11---도커-허브-또는-사설-레지스트리로-이동">1. 실습 11 - 도커 허브 또는 사설 레지스트리로 이동</h1>

<p>예를 들어 Docker Hub를 사용할 경우:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">login</span><span class="w">

</span><span class="n">docker</span><span class="w"> </span><span class="nx">tag</span><span class="w"> </span><span class="nx">openai-agents-workshop:1.0</span><span class="w"> </span><span class="se">`
</span><span class="w">  </span><span class="nx">YOUR_DOCKER_ID/openai-agents-workshop:1.0</span><span class="w">

</span><span class="n">docker</span><span class="w"> </span><span class="nx">push</span><span class="w"> </span><span class="nx">YOUR_DOCKER_ID/openai-agents-workshop:1.0</span><span class="w">
</span></code></pre></div></div>

<p>Public Cloud/On-Premises Linux 서버에서는:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull YOUR_DOCKER_ID/openai-agents-workshop:1.0

docker run <span class="nt">-d</span> <span class="se">\</span>
  <span class="nt">--name</span> openai-agents-workshop <span class="se">\</span>
  <span class="nt">-p</span> 8000:8000 <span class="se">\</span>
  <span class="nt">-e</span> <span class="nv">OPENAI_API_KEY</span><span class="o">=</span><span class="s2">"</span><span class="nv">$OPENAI_API_KEY</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">-e</span> <span class="nv">OPENAI_DEFAULT_MODEL</span><span class="o">=</span><span class="s2">"gpt-5.6-luna"</span> <span class="se">\</span>
  YOUR_DOCKER_ID/openai-agents-workshop:1.0
</code></pre></div></div>

<p>같은 이미지를 사용하므로 애플리케이션 코드와 파이썬 종속성(Python dependency)를 서버마다 다시 설치할 필요가 없다.</p>

<h1 id="2-실습-12---퍼블릭-클라우드-및-온-프레미시스-배포-개념">2. 실습 12 - 퍼블릭 클라우드 및 온-프레미시스 배포 개념</h1>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                  Container Registry
                         │
              openai-agents-workshop
                         │
          ┌──────────────┼──────────────┐
          │              │              │
          ▼              ▼              ▼
       AWS/Azure        GCP          On-Prem
          │              │              │
    Docker / K8s   Docker / K8s   Docker / K8s
          └──────────────┼──────────────┘
                         │
                         ▼
                     OpenAI API
</code></pre></div></div>

<p>배포 환경에서 달라지는 것은 주로 다음이다.</p>

<ul>
  <li>API Key / Secret 관리 방식</li>
  <li>네트워크와 Proxy/Firewall</li>
  <li>TLS/Ingress</li>
  <li>로그 수집</li>
  <li>Auto Scaling</li>
  <li>Container Registry</li>
  <li>Kubernetes resource 설정</li>
</ul>

<p>애플리케이션 이미 자체는 동일하게 유지할 수 있다.</p>

<h1 id="실습-13---arm64까지-지원하는-멀티-플랫폼-이미지">실습 13 - ARM64까지 지원하는 멀티 플랫폼 이미지</h1>

<p>AMD64 서버와 ARM64 서버를 동시에 지원하려면 Buildx를 사용할 수 있다.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">buildx</span><span class="w"> </span><span class="nx">build</span><span class="w"> </span><span class="se">`
</span><span class="w">  </span><span class="nt">--platform</span><span class="w"> </span><span class="nx">linux/amd64</span><span class="p">,</span><span class="nx">linux/arm64</span><span class="w"> </span><span class="se">`
</span><span class="w">  </span><span class="nt">-t</span><span class="w"> </span><span class="nx">YOUR_DOCKER_ID/openai-agents-workshop:1.0</span><span class="w"> </span><span class="se">`
</span><span class="w">  </span><span class="nt">--push</span><span class="w"> </span><span class="o">.</span><span class="w">
</span></code></pre></div></div>

<p>레지스트리에는 여러 아키텍처 매니페스트(architecture manifest)가 하나의 이미지 태그(image tag) 아래와 같이 저장된다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openai-agents-workshop:1.0
        │
        ├─ linux/amd64
        └─ linux/arm64
</code></pre></div></div>

<p>각 서버는 자신의 CPU 아키텍처에 맞는 변종(variant)를 자동으로 pull할 수 있다.</p>]]></content><author><name>{&quot;name&quot;=&gt;nil, &quot;avatar&quot;=&gt;nil, &quot;bio&quot;=&gt;&quot;서진호&quot;, &quot;location&quot;=&gt;&quot;Seoul. South Korea&quot;, &quot;email&quot;=&gt;&quot;synabreu@outlook.com&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Email&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-envelope-square&quot;, &quot;url&quot;=&gt;&quot;mailto:synabreu@outlook.com&quot;}, {&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://synabreu.github.io&quot;}, {&quot;label&quot;=&gt;&quot;Twitter&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-twitter-square&quot;}, {&quot;label&quot;=&gt;&quot;Facebook&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-facebook-square&quot;, &quot;url&quot;=&gt;&quot;https://www.facebook.com/jinho.seo.165&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/synabreu/&quot;}, {&quot;label&quot;=&gt;&quot;Instagram&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-instagram&quot;}]}</name><email>synabreu@outlook.com</email></author><category term="openai" /><category term="오픈AI" /><category term="OpenAI" /><category term="GPT-5.6" /><category term="agenticai" /><category term="aiagent" /><category term="openrouter" /><category term="docker" /><category term="powershell" /><category term="fastapi" /><category term="swagger-ui" /><category term="visual studio code" /><category term="Linux" /><category term="Windows11" /><category term="Kubernetes" /><category term="On-Premises" /><summary type="html"><![CDATA[이 실습은 도커 허브(Docker Hub) 또는 사설 레지스트리(Private Registery) 로 이동하는 방법이다. 참고로 도커 허브란 도커 이미지를 저장하고 공유하는 클라우드 저장소이다. 또한, 사설 레지스트리는 기업 조직 내부에서만 사용하는 비공개 도커 이미지 저장소를 말한다.]]></summary></entry><entry><title type="html">[실습] OpenAI 에이전트 도커 워크삽 (11)-문제해결</title><link href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(11)-%EB%AC%B8%EC%A0%9C%ED%95%B4%EA%B2%B0/" rel="alternate" type="text/html" title="[실습] OpenAI 에이전트 도커 워크삽 (11)-문제해결" /><published>2026-08-16T00:00:00+09:00</published><updated>2026-08-16T00:00:00+09:00</updated><id>https://synabreu.github.io/openai/%5B%EC%8B%A4%EC%8A%B5%5D%20OpenAI%20%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%20%EB%8F%84%EC%BB%A4%20%EC%9B%8C%ED%81%AC%EC%82%BD%20(11)-%EB%AC%B8%EC%A0%9C%ED%95%B4%EA%B2%B0</id><content type="html" xml:base="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(11)-%EB%AC%B8%EC%A0%9C%ED%95%B4%EA%B2%B0/"><![CDATA[<p>이 실습은 도커 허브(Docker Hub) 또는 사설 레지스트리(Private Registery) 로 이동하는 방법이다. 참고로 도커 허브란 도커 이미지를 저장하고 공유하는 클라우드 저장소이다. 또한, 사설 레지스트리는 기업 조직 내부에서만 사용하는 비공개 도커 이미지 저장소를 말한다.</p>

<h1 id="1-실습-11---도커-허브-또는-사설-레지스트리로-이동">1. 실습 11 - 도커 허브 또는 사설 레지스트리로 이동</h1>

<p>예를 들어 Docker Hub를 사용할 경우:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">login</span><span class="w">

</span><span class="n">docker</span><span class="w"> </span><span class="nx">tag</span><span class="w"> </span><span class="nx">openai-agents-workshop:1.0</span><span class="w"> </span><span class="se">`
</span><span class="w">  </span><span class="nx">YOUR_DOCKER_ID/openai-agents-workshop:1.0</span><span class="w">

</span><span class="n">docker</span><span class="w"> </span><span class="nx">push</span><span class="w"> </span><span class="nx">YOUR_DOCKER_ID/openai-agents-workshop:1.0</span><span class="w">
</span></code></pre></div></div>

<p>Public Cloud/On-Premises Linux 서버에서는:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull YOUR_DOCKER_ID/openai-agents-workshop:1.0

docker run <span class="nt">-d</span> <span class="se">\</span>
  <span class="nt">--name</span> openai-agents-workshop <span class="se">\</span>
  <span class="nt">-p</span> 8000:8000 <span class="se">\</span>
  <span class="nt">-e</span> <span class="nv">OPENAI_API_KEY</span><span class="o">=</span><span class="s2">"</span><span class="nv">$OPENAI_API_KEY</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">-e</span> <span class="nv">OPENAI_DEFAULT_MODEL</span><span class="o">=</span><span class="s2">"gpt-5.6-luna"</span> <span class="se">\</span>
  YOUR_DOCKER_ID/openai-agents-workshop:1.0
</code></pre></div></div>

<p>같은 이미지를 사용하므로 애플리케이션 코드와 파이썬 종속성(Python dependency)를 서버마다 다시 설치할 필요가 없다.</p>

<h1 id="2-실습-12---퍼블릭-클라우드-및-온-프레미시스-배포-개념">2. 실습 12 - 퍼블릭 클라우드 및 온-프레미시스 배포 개념</h1>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                  Container Registry
                         │
              openai-agents-workshop
                         │
          ┌──────────────┼──────────────┐
          │              │              │
          ▼              ▼              ▼
       AWS/Azure        GCP          On-Prem
          │              │              │
    Docker / K8s   Docker / K8s   Docker / K8s
          └──────────────┼──────────────┘
                         │
                         ▼
                     OpenAI API
</code></pre></div></div>

<p>배포 환경에서 달라지는 것은 주로 다음이다.</p>

<ul>
  <li>API Key / Secret 관리 방식</li>
  <li>네트워크와 Proxy/Firewall</li>
  <li>TLS/Ingress</li>
  <li>로그 수집</li>
  <li>Auto Scaling</li>
  <li>Container Registry</li>
  <li>Kubernetes resource 설정</li>
</ul>

<p>애플리케이션 이미 자체는 동일하게 유지할 수 있다.</p>

<h1 id="실습-13---arm64까지-지원하는-멀티-플랫폼-이미지">실습 13 - ARM64까지 지원하는 멀티 플랫폼 이미지</h1>

<p>AMD64 서버와 ARM64 서버를 동시에 지원하려면 Buildx를 사용할 수 있다.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">buildx</span><span class="w"> </span><span class="nx">build</span><span class="w"> </span><span class="se">`
</span><span class="w">  </span><span class="nt">--platform</span><span class="w"> </span><span class="nx">linux/amd64</span><span class="p">,</span><span class="nx">linux/arm64</span><span class="w"> </span><span class="se">`
</span><span class="w">  </span><span class="nt">-t</span><span class="w"> </span><span class="nx">YOUR_DOCKER_ID/openai-agents-workshop:1.0</span><span class="w"> </span><span class="se">`
</span><span class="w">  </span><span class="nt">--push</span><span class="w"> </span><span class="o">.</span><span class="w">
</span></code></pre></div></div>

<p>레지스트리에는 여러 아키텍처 매니페스트(architecture manifest)가 하나의 이미지 태그(image tag) 아래와 같이 저장된다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openai-agents-workshop:1.0
        │
        ├─ linux/amd64
        └─ linux/arm64
</code></pre></div></div>

<p>각 서버는 자신의 CPU 아키텍처에 맞는 변종(variant)를 자동으로 pull할 수 있다.</p>]]></content><author><name>{&quot;name&quot;=&gt;nil, &quot;avatar&quot;=&gt;nil, &quot;bio&quot;=&gt;&quot;서진호&quot;, &quot;location&quot;=&gt;&quot;Seoul. South Korea&quot;, &quot;email&quot;=&gt;&quot;synabreu@outlook.com&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Email&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-envelope-square&quot;, &quot;url&quot;=&gt;&quot;mailto:synabreu@outlook.com&quot;}, {&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://synabreu.github.io&quot;}, {&quot;label&quot;=&gt;&quot;Twitter&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-twitter-square&quot;}, {&quot;label&quot;=&gt;&quot;Facebook&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-facebook-square&quot;, &quot;url&quot;=&gt;&quot;https://www.facebook.com/jinho.seo.165&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/synabreu/&quot;}, {&quot;label&quot;=&gt;&quot;Instagram&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-instagram&quot;}]}</name><email>synabreu@outlook.com</email></author><category term="openai" /><category term="오픈AI" /><category term="OpenAI" /><category term="GPT-5.6" /><category term="agenticai" /><category term="aiagent" /><category term="openrouter" /><category term="docker" /><category term="powershell" /><category term="fastapi" /><category term="swagger-ui" /><category term="visual studio code" /><category term="Linux" /><category term="Windows11" /><category term="Kubernetes" /><category term="On-Premises" /><summary type="html"><![CDATA[이 실습은 도커 허브(Docker Hub) 또는 사설 레지스트리(Private Registery) 로 이동하는 방법이다. 참고로 도커 허브란 도커 이미지를 저장하고 공유하는 클라우드 저장소이다. 또한, 사설 레지스트리는 기업 조직 내부에서만 사용하는 비공개 도커 이미지 저장소를 말한다.]]></summary></entry><entry><title type="html">[실습] OpenAI 에이전트 도커 워크삽 (2)-환경설정</title><link href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(2)-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95/" rel="alternate" type="text/html" title="[실습] OpenAI 에이전트 도커 워크삽 (2)-환경설정" /><published>2026-08-16T00:00:00+09:00</published><updated>2026-08-16T00:00:00+09:00</updated><id>https://synabreu.github.io/openai/%5B%EC%8B%A4%EC%8A%B5%5D%20OpenAI%20%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%20%EB%8F%84%EC%BB%A4%20%EC%9B%8C%ED%81%AC%EC%82%BD%20(2)-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95</id><content type="html" xml:base="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(2)-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95/"><![CDATA[<p>OpenAI Agents SDK를 이용해서 앱 서비스를 개발하고 도커 이미지로 배포하기 위해 처음으로 해야 할 준비사항과 전체 아키텍처, 윈도우 11 운영체제 상에서 Python 환경 만드는 과정을 함께 진행해 보자!</p>

<h1 id="1-준비-사항">1. 준비 사항</h1>

<ul>
  <li>Windows 11</li>
  <li>Visual Studio Code</li>
  <li>Python 3.10 이상 (권장: Python 3.12)</li>
  <li>Docker Desktop</li>
  <li>OpenAI API Key</li>
  <li>PowerShell 7 권장</li>
</ul>

<p>Docker Desktop에서 <strong>Linux containers</strong>를 사용할 수 있어야 한다.</p>

<p>확인:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">version</span><span class="w">
</span><span class="n">docker</span><span class="w"> </span><span class="nx">info</span><span class="w"> </span><span class="nt">--format</span><span class="w"> </span><span class="s1">''</span><span class="w">
</span></code></pre></div></div>

<p>두 번째 명령 결과가 다음이면 된다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>linux
</code></pre></div></div>

<h1 id="2-전체-아키텍처">2. 전체 아키텍처</h1>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Windows 11 Native - Operating System 
  ├─ VS Code
  ├─ PowerShell
  ├─ Python 3.12 + .venv
  └─ Docker Desktop
         │
         │ docker build --platform linux/amd64
         ▼
   Linux Docker Image
   openai-agents-workshop:1.0
         │
         ├─ Docker Desktop
         ├─ Linux Docker Engine
         ├─ AWS / Azure / GCP
         └─ On-Premises / Kubernetes
                │
                ▼
             OpenAI API
</code></pre></div></div>

<p>그리고 프로젝트 구조는 다음과 같다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openai-agents-docker-workshop/
├─ app/
│  ├─ __init__.py
│  ├─ agents_app.py
│  └─ main.py
├─ scripts/
│  ├─ setup.ps1
│  ├─ run-native.ps1
│  ├─ build-image.ps1
│  ├─ run-container.ps1
│  ├─ test-api.ps1
│  └─ verify-image.ps1
├─ .dockerignore
├─ .env.example
├─ .gitignore
├─ docker-compose.yml
├─ Dockerfile
├─ requirements.txt
└─ README.md
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>파일명</th>
      <th>역할</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">app/__init__.py</code></td>
      <td><code class="language-plaintext highlighter-rouge">app</code> 디렉터리를 Python 패키지로 인식시키는 초기화 파일</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">app/agents_app.py</code></td>
      <td>OpenAI Agents SDK를 이용한 에이전트 생성, 실행 및 응답 처리 로직</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">app/main.py</code></td>
      <td>FastAPI 애플리케이션 진입점과 API 엔드포인트 정의</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">scripts/setup.ps1</code></td>
      <td>가상환경 생성과 Python 패키지 설치 등 초기 개발환경 구성</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">scripts/run-native.ps1</code></td>
      <td>애플리케이션을 로컬 Python 환경에서 직접 실행</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">scripts/build-image.ps1</code></td>
      <td>Dockerfile을 기반으로 Docker 이미지 빌드</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">scripts/run-container.ps1</code></td>
      <td>빌드된 이미지를 사용하여 Docker 컨테이너 실행</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">scripts/test-api.ps1</code></td>
      <td>실행 중인 애플리케이션의 API 요청 및 응답 테스트</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">scripts/verify-image.ps1</code></td>
      <td>Docker 이미지가 정상적으로 생성되고 실행 가능한지 검증</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.dockerignore</code></td>
      <td>Docker 빌드 컨텍스트에서 제외할 파일과 디렉터리 지정</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.env.example</code></td>
      <td>필요한 환경변수 형식을 보여주는 예제 파일. 실제 비밀값은 포함하지 않음</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.gitignore</code></td>
      <td>Git이 추적하지 않을 환경설정, 가상환경, 캐시, 보안 파일 등을 지정</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">docker-compose.yml</code></td>
      <td>애플리케이션 컨테이너의 실행 환경, 포트 및 환경변수 구성</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Dockerfile</code></td>
      <td>애플리케이션 Docker 이미지의 빌드 단계와 실행 명령 정의</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">requirements.txt</code></td>
      <td>프로젝트 실행에 필요한 Python 패키지와 버전 정의</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">README.md</code></td>
      <td>프로젝트 개요, 설치 방법, 실행 절차 및 API 사용법 안내</td>
    </tr>
  </tbody>
</table>

<h1 id="3-visual-studio-code-에서-프로젝트-열기">3. Visual Studio Code 에서 프로젝트 열기</h1>

<ul>
  <li>Github 주소에서 소스들을 다운로드 받아서 PowerShell에서 프로젝트 폴더로 이동한다.</li>
</ul>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cd</span><span class="w"> </span><span class="nx">C:\Projects\openai-agents-docker-workshop</span><span class="w">
</span><span class="n">git</span><span class="w"> </span><span class="nx">clone</span><span class="w"> </span><span class="nx">https://github.com/synabreu/openai-agents-docker-workshop.git</span><span class="w">
</span><span class="n">code</span><span class="w"> </span><span class="o">.</span><span class="w">
</span></code></pre></div></div>

<p>VS Code Terminal이 PowerShell인지 확인한다. 확인하는 방법은 아래의 그림과 같다.</p>

<h1 id="4-실습-1--windows-11-운영체제-상에서-python-환경-만들기">4. 실습 1- Windows 11 운영체제 상에서 Python 환경 만들기</h1>

<ul>
  <li>실습 환경을 자동 설정하려면 다음과 같이 하면 된다.</li>
</ul>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Set-ExecutionPolicy</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">Process</span><span class="w"> </span><span class="nx">Bypass</span><span class="w">
</span><span class="o">.</span><span class="n">\scripts\setup.ps1</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li>만일 그렇지 않고 수동으로 한다면 다음과 같다.</li>
</ul>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">python</span><span class="w"> </span><span class="nt">-m</span><span class="w"> </span><span class="nx">venv</span><span class="w"> </span><span class="o">.</span><span class="nf">venv</span><span class="w">
</span><span class="o">.</span><span class="nx">\.venv\Scripts\Activate.ps1</span><span class="w">
</span><span class="n">python</span><span class="w"> </span><span class="nt">-m</span><span class="w"> </span><span class="nx">pip</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nt">--upgrade</span><span class="w"> </span><span class="nx">pip</span><span class="w">
</span><span class="n">pip</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nt">-r</span><span class="w"> </span><span class="nx">requirements.txt</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li>둘 다 모두 설치 확인하려면 다음과 같다.</li>
</ul>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pip</span><span class="w"> </span><span class="nx">show</span><span class="w"> </span><span class="nx">openai-agents</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li>Agents SDK의 핵심 실행 흐름은 다음과 같다.</li>
</ul>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>사용자 입력
   ↓
Agent
   ↓
Runner
   ↓
Model / Tool / Handoff
   ↓
RunResult.final_output
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>단계 이름</th>
      <th>설명</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>User Input</td>
      <td>사용자가 질문이나 작업 요청을 입력함. 이 입력이 에이전트 실행의 시작점</td>
    </tr>
    <tr>
      <td>Agent</td>
      <td>지침, 모델, 도구, 핸드오프 등의 설정을 바탕으로 입력을 해석하고 수행할 작업을 결정함</td>
    </tr>
    <tr>
      <td>Runner</td>
      <td>에이전트의 실행 루프를 관리함. 모델 호출, 도구 실행, 핸드오프 처리 등을 반복하며 최종 결과를 생성함</td>
    </tr>
    <tr>
      <td>Model</td>
      <td>사용자 입력과 에이전트 지침을 분석해 응답하거나 다음에 실행할 도구 또는 핸드오프를 결정함</td>
    </tr>
    <tr>
      <td>Tool</td>
      <td>함수 호출, 데이터 조회, 계산 등 모델만으로 처리하기 어려운 외부 작업을 수행함. 실행 결과는 다시 에이전트에 전달됨</td>
    </tr>
    <tr>
      <td>Handoff</td>
      <td>현재 에이전트가 다른 전문 에이전트에 작업 제어권을 넘김. 복수 에이전트 협업에 사용됨.</td>
    </tr>
    <tr>
      <td>RunResult</td>
      <td>전체 에이전트 실행 과정에서 생성된 최종 출력과 관련 실행 정보를 담는 결과 객체임</td>
    </tr>
    <tr>
      <td>RunResult.final_output</td>
      <td>실행이 완료된 후 사용자에게 반환되는 최종 응답. 일반 텍스트뿐 아니라 지정된 구조화 데이터일 수도 있음</td>
    </tr>
  </tbody>
</table>

<h1 id="5-setupps1-파워셀-소스-분석">5. Setup.ps1 파워셀 소스 분석</h1>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span><span class="n">ErrorActionPreference</span> <span class="o">=</span> <span class="s">"Stop"</span>

<span class="n">Write</span><span class="o">-</span><span class="n">Host</span> <span class="s">"[1/4] Creating Python virtual environment..."</span>
<span class="n">python</span> <span class="o">-</span><span class="n">m</span> <span class="n">venv</span> <span class="p">.</span><span class="n">venv</span>

<span class="n">Write</span><span class="o">-</span><span class="n">Host</span> <span class="s">"[2/4] Activating virtual environment..."</span>
<span class="o">&amp;</span> <span class="p">.</span>\<span class="p">.</span><span class="n">venv</span>\<span class="n">Scripts</span>\<span class="n">Activate</span><span class="p">.</span><span class="n">ps1</span>

<span class="n">Write</span><span class="o">-</span><span class="n">Host</span> <span class="s">"[3/4] Upgrading pip..."</span>
<span class="n">python</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="o">--</span><span class="n">upgrade</span> <span class="n">pip</span>

<span class="n">Write</span><span class="o">-</span><span class="n">Host</span> <span class="s">"[4/4] Installing dependencies..."</span>
<span class="n">pip</span> <span class="n">install</span> <span class="o">-</span><span class="n">r</span> <span class="n">requirements</span><span class="p">.</span><span class="n">txt</span>

<span class="n">Write</span><span class="o">-</span><span class="n">Host</span> <span class="s">"Done. Next: `$env:OPENAI_API_KEY='YOUR_KEY'"</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">$ErrorActionPreference = "Stop"</code> : 스크립트 실행 중 오류가 발생하면 무시하지 말고 즉시 중단하라. 파워셀의 기본값은 <code class="language-plaintext highlighter-rouge">continue</code> 이다.</li>
  <li>`Write-Host’ : PowerShell 콘솔 화면에 문자열 출력</li>
</ul>]]></content><author><name>{&quot;name&quot;=&gt;nil, &quot;avatar&quot;=&gt;nil, &quot;bio&quot;=&gt;&quot;서진호&quot;, &quot;location&quot;=&gt;&quot;Seoul. South Korea&quot;, &quot;email&quot;=&gt;&quot;synabreu@outlook.com&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Email&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-envelope-square&quot;, &quot;url&quot;=&gt;&quot;mailto:synabreu@outlook.com&quot;}, {&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://synabreu.github.io&quot;}, {&quot;label&quot;=&gt;&quot;Twitter&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-twitter-square&quot;}, {&quot;label&quot;=&gt;&quot;Facebook&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-facebook-square&quot;, &quot;url&quot;=&gt;&quot;https://www.facebook.com/jinho.seo.165&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/synabreu/&quot;}, {&quot;label&quot;=&gt;&quot;Instagram&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-instagram&quot;}]}</name><email>synabreu@outlook.com</email></author><category term="openai" /><category term="오픈AI" /><category term="OpenAI" /><category term="GPT-5.6" /><category term="agenticai" /><category term="aiagent" /><category term="openrouter" /><category term="docker" /><category term="powershell" /><category term="fastapi" /><category term="swagger-ui" /><category term="visual studio code" /><category term="Linux" /><category term="Windows11" /><category term="Kubernetes" /><category term="On-Premises" /><summary type="html"><![CDATA[OpenAI Agents SDK를 이용해서 앱 서비스를 개발하고 도커 이미지로 배포하기 위해 처음으로 해야 할 준비사항과 전체 아키텍처, 윈도우 11 운영체제 상에서 Python 환경 만드는 과정을 함께 진행해 보자!]]></summary></entry><entry><title type="html">[실습] OpenAI 에이전트 도커 워크삽 (3)-agents 분석</title><link href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(3)-agents-%EB%B6%84%EC%84%9D/" rel="alternate" type="text/html" title="[실습] OpenAI 에이전트 도커 워크삽 (3)-agents 분석" /><published>2026-08-16T00:00:00+09:00</published><updated>2026-08-16T00:00:00+09:00</updated><id>https://synabreu.github.io/openai/%5B%EC%8B%A4%EC%8A%B5%5D%20OpenAI%20%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%20%EB%8F%84%EC%BB%A4%20%EC%9B%8C%ED%81%AC%EC%82%BD%20(3)-agents%20%EB%B6%84%EC%84%9D</id><content type="html" xml:base="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(3)-agents-%EB%B6%84%EC%84%9D/"><![CDATA[<p>두번째 실습에서는 OpenAI Key 설정하고 간단한 Simple Agent 를 분석하고 만든 다음, Function Tool 작성을 해 보자!</p>

<h1 id="1-실습-2---openai-key-설정">1. 실습 2 - OpenAI Key 설정</h1>

<p>현재 파워셀 세션에서만 환경변수를 설정한다.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">OPENAI_API_KEY</span><span class="o">=</span><span class="s2">"YOUR_OPENAI_API_KEY"</span><span class="w">
</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">OPENAI_DEFAULT_MODEL</span><span class="o">=</span><span class="s2">"gpt-5.6-luna"</span><span class="w">
</span></code></pre></div></div>

<blockquote>
  <p>일반 실습에서는 비용을 낮추기 위해 <code class="language-plaintext highlighter-rouge">gpt-5.6-luna</code>를 기본값으로 사용한다. 필요하면 <code class="language-plaintext highlighter-rouge">gpt-5.6-terra</code> 또는 <code class="language-plaintext highlighter-rouge">gpt-5.6-sol</code>로 바꿀 수 있다.</p>
</blockquote>

<p>확인할 때 API Key 전체를 출력하지 않는 편이 안전하다.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">OPENAI_API_KEY</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"OPENAI_API_KEY is set"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>보안을 위해 <code class="language-plaintext highlighter-rouge">.env</code>, Dockerfile, Git repository에 실제 API Key를 넣지 않는다.</p>

<h1 id="2-실습-3---simple-agent-분석">2. 실습 3 - Simple Agent 분석</h1>

<p><code class="language-plaintext highlighter-rouge">app/agents_app.py</code>의 첫 번째 에어전트는 다음 구조다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">simple_agent</span> <span class="o">=</span> <span class="n">Agent</span><span class="p">(</span>
    <span class="n">name</span><span class="o">=</span><span class="s">"Simple Assistant"</span><span class="p">,</span>
    <span class="n">model</span><span class="o">=</span><span class="n">MODEL</span><span class="p">,</span>
    <span class="n">instructions</span><span class="o">=</span><span class="s">"You are a beginner-friendly assistant..."</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div>

<p>실행은 다음 코드가 담당한다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">Runner</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">AGENTS</span><span class="p">[</span><span class="n">agent_name</span><span class="p">],</span> <span class="n">message</span><span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Runner.run()</code>은 비동기 환경에서 에이전트(Agent loop)를 수행하고 최종 결과를 반환한다.</p>

<h1 id="3-실습-4---function-tool-작성">3. 실습 4 - Function Tool 작성</h1>

<p>파이썬 함수를 에어전트가 호출할 수 있는 도구(Tool)로 만든다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">function_tool</span>
<span class="k">def</span> <span class="nf">calculate_total</span><span class="p">(</span><span class="n">price</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">quantity</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="n">total</span> <span class="o">=</span> <span class="n">price</span> <span class="o">*</span> <span class="n">quantity</span>
    <span class="k">return</span> <span class="sa">f</span><span class="s">"total=</span><span class="si">{</span><span class="n">total</span><span class="si">:</span><span class="p">.</span><span class="mi">2</span><span class="n">f</span><span class="si">}</span><span class="s">"</span>
</code></pre></div></div>

<p>그리고 Agent 클래스에 등록한다. <code class="language-plaintext highlighter-rouge">tool_agent</code> 안에 tools 변수에 `calculate_total’ 함수명을 넣는다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tool_agent</span> <span class="o">=</span> <span class="n">Agent</span><span class="p">(</span>
    <span class="n">name</span><span class="o">=</span><span class="s">"Shopping Assistant"</span><span class="p">,</span>
    <span class="n">model</span><span class="o">=</span><span class="n">MODEL</span><span class="p">,</span>
    <span class="n">tools</span><span class="o">=</span><span class="p">[</span><span class="n">calculate_total</span><span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<p>전체 핵심 흐름은 아래의 예와 같다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"12,500원 상품 3개 총액"
          ↓
       Agent
          ↓
  calculate_total Tool 호출
          ↓
       37,500
          ↓
      최종 응답
</code></pre></div></div>

<h1 id="4-실습-5---handoff--멀티-에이전트-작성">4. 실습 5 - Handoff / 멀티 에이전트 작성</h1>

<p>두 전문 Agent를 만든다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Deployment Triage Agent
        │
        ├── Public Cloud 질문 → Cloud Specialist
        │
        └── On-Prem 질문     → On-Prem Specialist
</code></pre></div></div>

<p>코드에서는 다음처럼 연결한다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">triage_agent</span> <span class="o">=</span> <span class="n">Agent</span><span class="p">(</span>
    <span class="n">name</span><span class="o">=</span><span class="s">"Deployment Triage Agent"</span><span class="p">,</span>
    <span class="n">model</span><span class="o">=</span><span class="n">MODEL</span><span class="p">,</span>
    <span class="n">instructions</span><span class="o">=</span><span class="s">"..."</span><span class="p">,</span>
    <span class="n">handoffs</span><span class="o">=</span><span class="p">[</span><span class="n">cloud_agent</span><span class="p">,</span> <span class="n">onprem_agent</span><span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Handoff는 단순 함수 호출과 다르다. 전문 Agent가 현재 turn의 active agent가 되어 응답을 이어갈 수 있다. 다시 말해, <code class="language-plaintext highlighter-rouge">handoff</code>는 현재 에이전트가 사용자의 요청을 더 적합한 전문 에이전트에게 넘기고, 그 에이전트가 이후 실행의 주체가 되도록 하는 기능입니다. 위의 설정은 다음과 같이 동작합니다.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Runner</code>가 먼저 <code class="language-plaintext highlighter-rouge">triage_agent</code>를 실행함.</li>
  <li><code class="language-plaintext highlighter-rouge">cloud_agent</code>와 <code class="language-plaintext highlighter-rouge">onprem_agent</code>로 전환할 수 있는 handoff 도구를 모델에 제공함.</li>
  <li><code class="language-plaintext highlighter-rouge">triage_agent</code>는 사용자 요청과 <code class="language-plaintext highlighter-rouge">instructions</code>를 바탕으로 적절한 에이전트를 선택함</li>
  <li>선택된 에이전트로 실행 제어권과 대화 문맥이 전달됨</li>
  <li><code class="language-plaintext highlighter-rouge">Runner</code>는 새 에이전트를 계속 실행하고 그 결과를 최종 출력으로 반환함</li>
</ul>

<p>예를 들면:</p>

<table>
  <thead>
    <tr>
      <th>사용자 요청</th>
      <th>예상 동작</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>“AWS에 Docker 이미지를 배포하고 싶어요.”</td>
      <td><code class="language-plaintext highlighter-rouge">cloud_agent</code>로 handoff</td>
    </tr>
    <tr>
      <td>“사내 서버에 컨테이너를 설치하고 싶어요.”</td>
      <td><code class="language-plaintext highlighter-rouge">onprem_agent</code>로 handoff</td>
    </tr>
    <tr>
      <td>요청이 불분명함</td>
      <td><code class="language-plaintext highlighter-rouge">triage_agent</code>가 추가 질문을 하거나 직접 응답</td>
    </tr>
  </tbody>
</table>

<p>흐름으로 표현하면 다음과 같다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>사용자 요청
    ↓
Deployment Triage Agent
    ├─ 클라우드 배포 요청 → cloud_agent
    └─ 사내 서버 배포 요청 → onprem_agent
                              ↓
                     전문 에이전트가 응답
                              ↓
                  RunResult.final_output
</code></pre></div></div>

<p>중요한 점은 <code class="language-plaintext highlighter-rouge">handoffs=[...]</code>에 등록했다고 해서 두 에이전트가 모두 실행되는 것은 아니라는 것이다. 모델이 요청에 맞는 에이전트를 선택했을 때만 handoff가 발생하며, 발생하지 않으면 <code class="language-plaintext highlighter-rouge">triage_agent</code>가 직접 응답할 수도 있다.</p>

<p>또한 handoff는 단순히 하위 작업을 부탁하고 결과를 돌려받는 것이 아니라, 대화의 주도권 자체를 대상 에이전트로 넘기는 방식이다. OpenAI 공식 예제에서도 triage 에이전트가 요청 언어를 판별해 적절한 언어 전문 에이전트로 전환한다.</p>

<h1 id="5-전체-소스">5. 전체 소스</h1>

<table>
  <thead>
    <tr>
      <th>사용자 요청</th>
      <th>예상 동작</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>init</strong>.py</td>
      <td>아무런 내용이 없는 데, 이는 오류나 미완성 파일이 아니라 “이 폴더는 Python 패키지이며 별도의 초기화 동작은 없다”는 뜻</td>
    </tr>
    <tr>
      <td>agents_app.py</td>
      <td>agent 클래스 관련 파일</td>
    </tr>
    <tr>
      <td>main.py</td>
      <td>파이썬 main 함수 실행</td>
    </tr>
  </tbody>
</table>

<h2 id="51-agents_apppy-파일-전체-분석">5.1 agents_app.py 파일 전체 분석</h2>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span> <span class="c1"># 운영체제의 환경변수를 읽기 위한 Python 표준 라이브러리
</span>
<span class="c1"># Agent:
# 모델, 지침, 도구, handoff 등을 구성하는 에이전트 클래스
#
# Runner:
# Agent를 실행하고 모델 호출, 도구 실행, handoff 과정을 관리하는 실행기
#
# function_tool:
# 일반 Python 함수를 Agent가 호출할 수 있는 Function Tool로 변환하는 데코레이터
</span><span class="kn">from</span> <span class="nn">agents</span> <span class="kn">import</span> <span class="n">Agent</span><span class="p">,</span> <span class="n">Runner</span><span class="p">,</span> <span class="n">function_tool</span>


<span class="c1"># OPENAI_DEFAULT_MODEL 환경변수의 값을 가져옴
# 환경변수가 없으면 기본값으로 "gpt-5.6-luna"를 사용함
</span><span class="n">MODEL</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">getenv</span><span class="p">(</span><span class="s">"OPENAI_DEFAULT_MODEL"</span><span class="p">,</span> <span class="s">"gpt-5.6-luna"</span><span class="p">)</span>


<span class="c1"># 이 데코레이터(@)는 calculate_total()을 Agent가 호출할 수 있는 Function Tool로 변환
</span><span class="o">@</span><span class="n">function_tool</span>
<span class="k">def</span> <span class="nf">calculate_total</span><span class="p">(</span><span class="n">price</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">quantity</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="s">"""
    상품 가격과 수량을 곱하여 총액을 계산함.

    Args:
        price: 상품 하나의 가격
        quantity: 상품 수량

    Returns:
        계산된 총액을 문자열로 반환
    """</span>

    <span class="c1"># 가격과 수량을 곱하여 총액 계산
</span>    <span class="n">total</span> <span class="o">=</span> <span class="n">price</span> <span class="o">*</span> <span class="n">quantity</span>

    <span class="c1"># 소수점 둘째 자리까지 표시한 문자열 반환
</span>    <span class="k">return</span> <span class="sa">f</span><span class="s">"total=</span><span class="si">{</span><span class="n">total</span><span class="si">:</span><span class="p">.</span><span class="mi">2</span><span class="n">f</span><span class="si">}</span><span class="s">"</span>


<span class="c1"># 일반적인 질문에 답변하는 기본 에이전트 객체
</span><span class="n">simple_agent</span> <span class="o">=</span> <span class="n">Agent</span><span class="p">(</span>
    <span class="c1"># Agent를 식별하는 이름
</span>    <span class="n">name</span><span class="o">=</span><span class="s">"simple_assistant"</span><span class="p">,</span>

    <span class="c1"># Agent가 사용할 모델
</span>    <span class="n">model</span><span class="o">=</span><span class="n">MODEL</span><span class="p">,</span>

    <span class="c1"># Agent의 역할과 응답 방식을 정의하는 지침
</span>    <span class="n">instructions</span><span class="o">=</span><span class="p">(</span>
        <span class="s">"You are a beginner-friendly assistant. "</span>
        <span class="s">"Answer clearly and briefly."</span>
    <span class="p">),</span>
<span class="p">)</span>


<span class="c1"># 상품 가격 계산을 지원하는 에이전트 객체
</span><span class="n">tool_agent</span> <span class="o">=</span> <span class="n">Agent</span><span class="p">(</span>
    <span class="n">name</span><span class="o">=</span><span class="s">"shopping_assistant"</span><span class="p">,</span>
    <span class="n">model</span><span class="o">=</span><span class="n">MODEL</span><span class="p">,</span>

    <span class="c1"># 가격과 수량의 곱셈이 필요한 경우 calculate_total 도구를 사용하도록 지시함
</span>    <span class="n">instructions</span><span class="o">=</span><span class="p">(</span>
        <span class="s">"You help users with simple shopping calculations. "</span>
        <span class="s">"Use the calculate_total tool whenever multiplication "</span>
        <span class="s">"of price and quantity is needed."</span>
    <span class="p">),</span>

    <span class="c1"># 이 Agent가 사용할 수 있는 Function Tool 목록
</span>    <span class="n">tools</span><span class="o">=</span><span class="p">[</span><span class="n">calculate_total</span><span class="p">],</span>
<span class="p">)</span>


<span class="c1"># AWS, Azure, GCP 등 퍼블릭 클라우드 배포를 담당하는 전문 에이전트
</span><span class="n">cloud_agent</span> <span class="o">=</span> <span class="n">Agent</span><span class="p">(</span>
    <span class="n">name</span><span class="o">=</span><span class="s">"cloud_specialist"</span><span class="p">,</span>
    <span class="n">model</span><span class="o">=</span><span class="n">MODEL</span><span class="p">,</span>
    <span class="n">instructions</span><span class="o">=</span><span class="p">(</span>
        <span class="s">"You are a cloud deployment specialist. "</span>
        <span class="s">"Explain container deployment concepts for AWS, Azure, "</span>
        <span class="s">"and GCP in beginner-friendly Korean."</span>
    <span class="p">),</span>
<span class="p">)</span>


<span class="c1"># 사내 서버와 데이터센터 배포를 담당하는 전문 에이전트
</span><span class="n">onprem_agent</span> <span class="o">=</span> <span class="n">Agent</span><span class="p">(</span>
    <span class="n">name</span><span class="o">=</span><span class="s">"on_prem_specialist"</span><span class="p">,</span>
    <span class="n">model</span><span class="o">=</span><span class="n">MODEL</span><span class="p">,</span>
    <span class="n">instructions</span><span class="o">=</span><span class="p">(</span>
        <span class="s">"You are an on-premises container deployment specialist. "</span>
        <span class="s">"Explain Docker and Kubernetes deployment concepts "</span>
        <span class="s">"in beginner-friendly Korean."</span>
    <span class="p">),</span>
<span class="p">)</span>


<span class="c1"># 사용자의 배포 질문을 분류하는 Triage 에이전트
</span><span class="n">triage_agent</span> <span class="o">=</span> <span class="n">Agent</span><span class="p">(</span>
    <span class="n">name</span><span class="o">=</span><span class="s">"deployment_triage_agent"</span><span class="p">,</span>
    <span class="n">model</span><span class="o">=</span><span class="n">MODEL</span><span class="p">,</span>

    <span class="c1"># 사용자 요청을 분석하여 적절한 전문 Agent를 선택하도록 지시함
</span>    <span class="n">instructions</span><span class="o">=</span><span class="p">(</span>
        <span class="s">"Classify the user's deployment question. "</span>
        <span class="s">"If it is mainly about a public cloud, "</span>
        <span class="s">"hand off to Cloud Specialist. "</span>
        <span class="s">"If it is mainly about an on-premises environment, "</span>
        <span class="s">"hand off to On-Prem Specialist. "</span>
        <span class="s">"If neither is needed, answer directly."</span>
    <span class="p">),</span>

    <span class="c1"># Triage Agent가 실행 제어권을 넘길 수 있는 Agent 목록
</span>    <span class="c1">#
</span>    <span class="c1"># 클라우드 질문이면 cloud_agent,
</span>    <span class="c1"># 사내 환경 질문이면 onprem_agent로 handoff할 수 있음
</span>    <span class="n">handoffs</span><span class="o">=</span><span class="p">[</span><span class="n">cloud_agent</span><span class="p">,</span> <span class="n">onprem_agent</span><span class="p">],</span>
<span class="p">)</span>


<span class="c1"># 외부에서 전달받은 문자열 이름을 실제 Agent 객체와 연결하는 딕셔너리
#
# 예:
# "simple" → simple_agent
# "tool"   → tool_agent
# "triage" → triage_agent
</span><span class="n">AGENTS</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"simple"</span><span class="p">:</span> <span class="n">simple_agent</span><span class="p">,</span>
    <span class="s">"tool"</span><span class="p">:</span> <span class="n">tool_agent</span><span class="p">,</span>
    <span class="s">"triage"</span><span class="p">:</span> <span class="n">triage_agent</span><span class="p">,</span>
<span class="p">}</span>


<span class="c1"># 선택된 Agent를 비동기로 실행하는 애플리케이션 함수
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">run_agent</span><span class="p">(</span><span class="n">agent_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
    <span class="s">"""
    Agent 이름과 사용자 메시지를 받아 해당 Agent를 실행합니다.

    Args:
        agent_name: 실행할 Agent 이름
        message: 사용자가 입력한 메시지

    Returns:
        최초 Agent 이름, 마지막으로 응답한 Agent 이름,
        최종 응답을 포함한 딕셔너리
    """</span>

    <span class="c1"># 등록되지 않은 Agent 이름이 전달되면 실행을 중단합니다.
</span>    <span class="k">if</span> <span class="n">agent_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">AGENTS</span><span class="p">:</span>
        <span class="k">raise</span> <span class="nb">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s">"Unknown agent: </span><span class="si">{</span><span class="n">agent_name</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

    <span class="c1"># 선택된 Agent와 사용자 메시지를 Runner에 전달함
</span>    <span class="c1">#
</span>    <span class="c1"># Runner는 다음 과정을 관리함
</span>    <span class="c1"># 1. 모델 호출
</span>    <span class="c1"># 2. Function Tool 실행
</span>    <span class="c1"># 3. Agent handoff
</span>    <span class="c1"># 4. 최종 응답 생성
</span>    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">Runner</span><span class="p">.</span><span class="n">run</span><span class="p">(</span>
        <span class="n">AGENTS</span><span class="p">[</span><span class="n">agent_name</span><span class="p">],</span>
        <span class="n">message</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="c1"># API에서 사용하기 편한 딕셔너리 형식으로 결과를 반환함
</span>    <span class="k">return</span> <span class="p">{</span>
        <span class="c1"># 사용자가 처음 선택한 에이전트
</span>        <span class="s">"agent"</span><span class="p">:</span> <span class="n">agent_name</span><span class="p">,</span>

        <span class="c1"># handoff를 포함한 전체 실행에서 마지막으로 동작한 에이전트
</span>        <span class="s">"last_agent"</span><span class="p">:</span> <span class="p">(</span>
            <span class="n">result</span><span class="p">.</span><span class="n">last_agent</span><span class="p">.</span><span class="n">name</span>
            <span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="n">last_agent</span>
            <span class="k">else</span> <span class="bp">None</span>
        <span class="p">),</span>

        <span class="c1"># 사용자에게 전달할 최종 응답
</span>        <span class="s">"response"</span><span class="p">:</span> <span class="n">result</span><span class="p">.</span><span class="n">final_output</span><span class="p">,</span>
    <span class="p">}</span>
</code></pre></div></div>

<h1 id="6-참고-자료">6. 참고 자료</h1>

<p><a href="https://platform.openai.com/docs/quickstart/make-your-first-api-request">OpenAI Developer Quickstart</a></p>]]></content><author><name>{&quot;name&quot;=&gt;nil, &quot;avatar&quot;=&gt;nil, &quot;bio&quot;=&gt;&quot;서진호&quot;, &quot;location&quot;=&gt;&quot;Seoul. South Korea&quot;, &quot;email&quot;=&gt;&quot;synabreu@outlook.com&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Email&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-envelope-square&quot;, &quot;url&quot;=&gt;&quot;mailto:synabreu@outlook.com&quot;}, {&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://synabreu.github.io&quot;}, {&quot;label&quot;=&gt;&quot;Twitter&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-twitter-square&quot;}, {&quot;label&quot;=&gt;&quot;Facebook&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-facebook-square&quot;, &quot;url&quot;=&gt;&quot;https://www.facebook.com/jinho.seo.165&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/synabreu/&quot;}, {&quot;label&quot;=&gt;&quot;Instagram&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-instagram&quot;}]}</name><email>synabreu@outlook.com</email></author><category term="openai" /><category term="오픈AI" /><category term="OpenAI" /><category term="GPT-5.6" /><category term="agenticai" /><category term="aiagent" /><category term="openrouter" /><category term="docker" /><category term="powershell" /><category term="fastapi" /><category term="swagger-ui" /><category term="visual studio code" /><category term="Linux" /><category term="Windows11" /><category term="Kubernetes" /><category term="On-Premises" /><summary type="html"><![CDATA[두번째 실습에서는 OpenAI Key 설정하고 간단한 Simple Agent 를 분석하고 만든 다음, Function Tool 작성을 해 보자!]]></summary></entry><entry><title type="html">[실습] OpenAI 에이전트 도커 워크삽 (4)-main 소스분석</title><link href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(4)-main-%EC%86%8C%EC%8A%A4%EB%B6%84%EC%84%9D/" rel="alternate" type="text/html" title="[실습] OpenAI 에이전트 도커 워크삽 (4)-main 소스분석" /><published>2026-08-16T00:00:00+09:00</published><updated>2026-08-16T00:00:00+09:00</updated><id>https://synabreu.github.io/openai/%5B%EC%8B%A4%EC%8A%B5%5D%20OpenAI%20%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%20%EB%8F%84%EC%BB%A4%20%EC%9B%8C%ED%81%AC%EC%82%BD%20(4)-main%20%EC%86%8C%EC%8A%A4%EB%B6%84%EC%84%9D</id><content type="html" xml:base="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(4)-main-%EC%86%8C%EC%8A%A4%EB%B6%84%EC%84%9D/"><![CDATA[<p>이 블로그는 main.py 파일 소스를 분석하고 내부의 get 과 post 데코레이터 함수를 어떻게 사용하는 지 분석한다.</p>

<h1 id="1-mainpy-파일-소스-분석">1. main.py 파일 소스 분석</h1>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># FastAPI는 웹 API 애플리케이션을 생성하는 클래스
# HTTPException은 클라이언트에 HTTP 오류 응답을 반환하는 예외 클래스
</span><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span><span class="p">,</span> <span class="n">HTTPException</span>

<span class="c1"># BaseModel은 요청과 응답 데이터의 구조를 정의하는 Pydantic 기본 클래스
# Field는 필드의 유효성 검사 조건과 문서 예시를 설정하는 함수
</span><span class="kn">from</span> <span class="nn">pydantic</span> <span class="kn">import</span> <span class="n">BaseModel</span><span class="p">,</span> <span class="n">Field</span>

<span class="c1"># 지정한 Agent를 실행하고 결과를 반환하는 함수를 가져온다.
</span><span class="kn">from</span> <span class="nn">app.agents_app</span> <span class="kn">import</span> <span class="n">run_agent</span>


<span class="c1"># FastAPI 애플리케이션 객체를 생성하고, 설정한 정보는 Swagger UI와 OpenAPI 문서에 표시된다.
</span><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">(</span>
    <span class="c1"># API 문서에 표시되는 애플리케이션 이름
</span>    <span class="n">title</span><span class="o">=</span><span class="s">"OpenAI Agents SDK Docker Workshop"</span><span class="p">,</span>

    <span class="c1"># API 버전 정보
</span>    <span class="n">version</span><span class="o">=</span><span class="s">"1.0.0"</span><span class="p">,</span>

    <span class="c1"># 애플리케이션의 목적과 실행 환경
</span>    <span class="n">description</span><span class="o">=</span><span class="p">(</span>
        <span class="s">"Windows Native development -&gt; "</span>
        <span class="s">"Linux Docker image hands-on workshop"</span>
    <span class="p">),</span>
<span class="p">)</span>


<span class="c1"># Agent API가 전달받을 요청 본문의 데이터 구조를 정의함
</span><span class="k">class</span> <span class="nc">ChatRequest</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="c1"># 사용자가 Agent에게 보낼 메시지이며, 최소 한 글자 이상 입력하도록 검사한다.
</span>    <span class="c1"># examples는 Swagger UI에 표시할 요청 예시이다.
</span>    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
        <span class="n">min_length</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
        <span class="n">examples</span><span class="o">=</span><span class="p">[</span><span class="s">"안녕하세요. 이 앱을 설명해줘."</span><span class="p">],</span>
    <span class="p">)</span>


<span class="c1"># HTTP GET 방식의 루트 경로를 등록함
</span><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"/"</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">root</span><span class="p">():</span>
    <span class="s">"""
    애플리케이션의 기본 정보와 주요 API 경로를 반환한다.
    """</span>

    <span class="k">return</span> <span class="p">{</span>
        <span class="c1"># 애플리케이션 이름 반환
</span>        <span class="s">"name"</span><span class="p">:</span> <span class="s">"OpenAI Agents SDK Docker Workshop"</span><span class="p">,</span>

        <span class="c1"># Swagger API 문서의 경로 반환
</span>        <span class="s">"docs"</span><span class="p">:</span> <span class="s">"/docs"</span><span class="p">,</span>

        <span class="c1"># 서버 상태 확인 경로 반환
</span>        <span class="s">"health"</span><span class="p">:</span> <span class="s">"/health"</span><span class="p">,</span>

        <span class="c1"># 실행할 수 있는 에이전트 이름 반환
</span>        <span class="s">"agents"</span><span class="p">:</span> <span class="p">[</span><span class="s">"simple"</span><span class="p">,</span> <span class="s">"tool"</span><span class="p">,</span> <span class="s">"triage"</span><span class="p">],</span>
    <span class="p">}</span>


<span class="c1"># HTTP GET 방식의 서버 상태 확인 경로를 등록
</span><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"/health"</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">health</span><span class="p">():</span>
    <span class="s">"""
    애플리케이션이 정상적으로 실행 중인지 확인한다.
    """</span>

    <span class="k">return</span> <span class="p">{</span><span class="s">"status"</span><span class="p">:</span> <span class="s">"ok"</span><span class="p">}</span>


<span class="c1"># HTTP GET 방식으로 특정 에이전트를 실행하는 경로를 등록함
# {agent_name}에는 simple, tool, triage 등의 Agent 이름이 들어간다.
</span><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"/agents/{agent_name}"</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">chat</span><span class="p">(</span><span class="n">agent_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">request</span><span class="p">:</span> <span class="n">ChatRequest</span><span class="p">):</span>
    <span class="s">"""
    URL에서 Agent 이름을 받고 요청 본문에서 메시지를 받아 Agent를 실행한다.
    """</span>

    <span class="k">try</span><span class="p">:</span>
        <span class="c1"># 지정된 Agent를 비동기로 실행하고 결과를 반환함
</span>        <span class="k">return</span> <span class="k">await</span> <span class="n">run_agent</span><span class="p">(</span>
            <span class="n">agent_name</span><span class="p">,</span>
            <span class="n">request</span><span class="p">.</span><span class="n">message</span><span class="p">,</span>
        <span class="p">)</span>

    <span class="k">except</span> <span class="nb">ValueError</span> <span class="k">as</span> <span class="n">exc</span><span class="p">:</span>
        <span class="c1"># 존재하지 않는 Agent 이름이 전달되면 404 오류를 반환함
</span>        <span class="k">raise</span> <span class="n">HTTPException</span><span class="p">(</span>
            <span class="n">status_code</span><span class="o">=</span><span class="mi">404</span><span class="p">,</span>
            <span class="n">detail</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">exc</span><span class="p">),</span>
        <span class="p">)</span> <span class="k">from</span> <span class="n">exc</span>

    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">exc</span><span class="p">:</span>
        <span class="c1"># Agent 실행 중 예상하지 못한 오류가 발생하면 500 오류를 반환함
</span>        <span class="k">raise</span> <span class="n">HTTPException</span><span class="p">(</span>
            <span class="n">status_code</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span>
            <span class="n">detail</span><span class="o">=</span><span class="sa">f</span><span class="s">"Agent execution failed: </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
        <span class="p">)</span> <span class="k">from</span> <span class="n">exc</span>


<span class="c1"># HTTP POST 방식으로 특정 Agent를 실행하는 경로를 등록한다.
</span><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="s">"/agents/{agent_name}"</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">chat</span><span class="p">(</span><span class="n">agent_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">request</span><span class="p">:</span> <span class="n">ChatRequest</span><span class="p">):</span>
    <span class="s">"""
    URL에서 Agent 이름을 받고 JSON 요청 본문에서 메시지를 받아 Agent를 실행한다.
    """</span>

    <span class="k">try</span><span class="p">:</span>
        <span class="c1"># 지정된 Agent를 비동기로 실행하고 결과를 반환함
</span>        <span class="k">return</span> <span class="k">await</span> <span class="n">run_agent</span><span class="p">(</span>
            <span class="n">agent_name</span><span class="p">,</span>
            <span class="n">request</span><span class="p">.</span><span class="n">message</span><span class="p">,</span>
        <span class="p">)</span>

    <span class="k">except</span> <span class="nb">ValueError</span> <span class="k">as</span> <span class="n">exc</span><span class="p">:</span>
        <span class="c1"># 존재하지 않는 Agent 이름이 전달되면 404 오류를 반환함
</span>        <span class="k">raise</span> <span class="n">HTTPException</span><span class="p">(</span>
            <span class="n">status_code</span><span class="o">=</span><span class="mi">404</span><span class="p">,</span>
            <span class="n">detail</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">exc</span><span class="p">),</span>
        <span class="p">)</span> <span class="k">from</span> <span class="n">exc</span>

    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">exc</span><span class="p">:</span>
        <span class="c1"># Agent 실행 중 예상하지 못한 오류가 발생하면 500 오류를 반환함
</span>        <span class="k">raise</span> <span class="n">HTTPException</span><span class="p">(</span>
            <span class="n">status_code</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span>
            <span class="n">detail</span><span class="o">=</span><span class="sa">f</span><span class="s">"Agent execution failed: </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
        <span class="p">)</span> <span class="k">from</span> <span class="n">exc</span>
</code></pre></div></div>

<h1 id="2-getpost-함수">2. get/post 함수</h1>

<p>GET과 POST 함수 이름이 모두 chat으로 동일하다. FastAPI 경로는 데코레이터가 등록하므로 실행될 수 있지만, 코드 가독성과 API 문서의 작업 식별을 위해 다음처럼 서로 다른 이름을 사용하는 것이 좋다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"/agents/{agent_name}"</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">chat_get</span><span class="p">(</span><span class="n">agent_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">request</span><span class="p">:</span> <span class="n">ChatRequest</span><span class="p">):</span>
    <span class="p">...</span>


<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="s">"/agents/{agent_name}"</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">chat_post</span><span class="p">(</span><span class="n">agent_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">request</span><span class="p">:</span> <span class="n">ChatRequest</span><span class="p">):</span>
    <span class="p">...</span>
</code></pre></div></div>

<p>또한 요청 본문을 전달하는 API는 일반적으로 GET보다 POST 방식을 사용한다. 따라서 실제 서비스에서는 POST 경로만 제공하는 구성이 더 자연스럽다.</p>]]></content><author><name>{&quot;name&quot;=&gt;nil, &quot;avatar&quot;=&gt;nil, &quot;bio&quot;=&gt;&quot;서진호&quot;, &quot;location&quot;=&gt;&quot;Seoul. South Korea&quot;, &quot;email&quot;=&gt;&quot;synabreu@outlook.com&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Email&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-envelope-square&quot;, &quot;url&quot;=&gt;&quot;mailto:synabreu@outlook.com&quot;}, {&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://synabreu.github.io&quot;}, {&quot;label&quot;=&gt;&quot;Twitter&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-twitter-square&quot;}, {&quot;label&quot;=&gt;&quot;Facebook&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-facebook-square&quot;, &quot;url&quot;=&gt;&quot;https://www.facebook.com/jinho.seo.165&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/synabreu/&quot;}, {&quot;label&quot;=&gt;&quot;Instagram&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-instagram&quot;}]}</name><email>synabreu@outlook.com</email></author><category term="openai" /><category term="오픈AI" /><category term="OpenAI" /><category term="GPT-5.6" /><category term="agenticai" /><category term="aiagent" /><category term="openrouter" /><category term="docker" /><category term="powershell" /><category term="fastapi" /><category term="swagger-ui" /><category term="visual studio code" /><category term="Linux" /><category term="Windows11" /><category term="Kubernetes" /><category term="On-Premises" /><summary type="html"><![CDATA[이 블로그는 main.py 파일 소스를 분석하고 내부의 get 과 post 데코레이터 함수를 어떻게 사용하는 지 분석한다.]]></summary></entry><entry><title type="html">[실습] OpenAI 에이전트 도커 워크삽 (5)-FastAPI 서비스</title><link href="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(5)-FastAPI-%EC%84%9C%EB%B9%84%EC%8A%A4/" rel="alternate" type="text/html" title="[실습] OpenAI 에이전트 도커 워크삽 (5)-FastAPI 서비스" /><published>2026-08-16T00:00:00+09:00</published><updated>2026-08-16T00:00:00+09:00</updated><id>https://synabreu.github.io/openai/%5B%EC%8B%A4%EC%8A%B5%5D%20OpenAI%20%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%20%EB%8F%84%EC%BB%A4%20%EC%9B%8C%ED%81%AC%EC%82%BD%20(5)-FastAPI%20%EC%84%9C%EB%B9%84%EC%8A%A4</id><content type="html" xml:base="https://synabreu.github.io/openai/%EC%8B%A4%EC%8A%B5-OpenAI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%8F%84%EC%BB%A4-%EC%9B%8C%ED%81%AC%EC%82%BD-(5)-FastAPI-%EC%84%9C%EB%B9%84%EC%8A%A4/"><![CDATA[<p>이제 FastAPI로 서비스화는 방법에 대해 설명하고 윈도우 환경에서 실행하는 run-native 파워셀 파일과 API를 테스트하는 test-api 파워셀 파일을 각각 내부에 어떻게 구성되어 있는지 알아보고 실행해 보자!</p>

<h1 id="1-실습-6---fastapi로-서비스화">1. 실습 6 - FastAPI로 서비스화</h1>

<p><code class="language-plaintext highlighter-rouge">app/main.py</code>는 Agents SDK를 HTTP API로 노출한다.</p>

<p>주요 엔드포인트:</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>Endpoint</th>
      <th>기능</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/health</code></td>
      <td>상태 확인</td>
    </tr>
    <tr>
      <td>POST</td>
      <td><code class="language-plaintext highlighter-rouge">/agents/simple</code></td>
      <td>Simple Agent</td>
    </tr>
    <tr>
      <td>POST</td>
      <td><code class="language-plaintext highlighter-rouge">/agents/tool</code></td>
      <td>Tool Calling</td>
    </tr>
    <tr>
      <td>POST</td>
      <td><code class="language-plaintext highlighter-rouge">/agents/triage</code></td>
      <td>Handoff / Multi-Agent</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/docs</code></td>
      <td>Swagger UI</td>
    </tr>
  </tbody>
</table>

<p>Windows 11 운영체제 환경에서 실행한다.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\scripts\run-native.ps1</span><span class="w">
</span></code></pre></div></div>

<p>웹 브라우저:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://localhost:8010/docs
</code></pre></div></div>

<p>다른 파워셀 창에서 테스트한다.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\scripts\test-api.ps1</span><span class="w">
</span></code></pre></div></div>

<p>여기까지는 <strong>Docker를 사용하지 않은 Windows 네이티브 개발 및 디버깅</strong>이다.</p>

<h2 id="6-1-run-nativeps1-분석">6-1. run-native.ps1 분석</h2>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span><span class="n">ErrorActionPreference</span> <span class="o">=</span> <span class="s">"Stop"</span>

<span class="k">if</span> <span class="p">(</span><span class="o">-</span><span class="ow">not</span> <span class="err">$</span><span class="n">env</span><span class="p">:</span><span class="n">OPENAI_API_KEY</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">throw</span> <span class="s">"OPENAI_API_KEY is not set. Example: `$env:OPENAI_API_KEY='sk-...'"</span>
<span class="p">}</span>

<span class="k">if</span> <span class="p">(</span><span class="o">-</span><span class="ow">not</span> <span class="err">$</span><span class="n">env</span><span class="p">:</span><span class="n">OPENAI_DEFAULT_MODEL</span><span class="p">)</span> <span class="p">{</span>
    <span class="err">$</span><span class="n">env</span><span class="p">:</span><span class="n">OPENAI_DEFAULT_MODEL</span> <span class="o">=</span> <span class="s">"gpt-5.6-luna"</span>
<span class="p">}</span>

<span class="o">&amp;</span> <span class="p">.</span>\<span class="p">.</span><span class="n">venv</span>\<span class="n">Scripts</span>\<span class="n">Activate</span><span class="p">.</span><span class="n">ps1</span>
<span class="n">uvicorn</span> <span class="n">app</span><span class="p">.</span><span class="n">main</span><span class="p">:</span><span class="n">app</span> <span class="o">--</span><span class="n">host</span> <span class="mf">127.0</span><span class="p">.</span><span class="mf">0.1</span> <span class="o">--</span><span class="n">port</span> <span class="mi">8010</span> <span class="o">--</span><span class="nb">reload</span>
</code></pre></div></div>

<p>위의 소스는 윈도우 11 환경으로 FastAPI 기반 OpenAI Agents SDK 애플리케이션을 실행하는 역할을 한다. OpenAI API Key 존재 여부 확인과 <code class="language-plaintext highlighter-rouge">gpt-5.6-luna</code>를 기본 모델 지정한다. 여기서 OpenAI API Key 를 보안 관계상 직접 입력하지 말고 파워쉘의 environment 변수를 통해 입력한다.</p>

<table>
  <thead>
    <tr>
      <th>구성요소</th>
      <th>종류</th>
      <th>설명</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">uvicorn</code></td>
      <td>실행 명령</td>
      <td>FastAPI 같은 ASGI 애플리케이션을 실행하는 웹 서버이다.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">app.main:app</code></td>
      <td>애플리케이션 인자</td>
      <td>실행할 FastAPI 애플리케이션의 위치를 지정한다.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">app.main</code></td>
      <td>Python 모듈</td>
      <td><code class="language-plaintext highlighter-rouge">app</code> 디렉터리의 <code class="language-plaintext highlighter-rouge">main.py</code> 파일을 의미한다.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">:app</code></td>
      <td>객체 이름</td>
      <td><code class="language-plaintext highlighter-rouge">main.py</code> 안에 선언된 <code class="language-plaintext highlighter-rouge">app = FastAPI(...)</code> 객체를 의미한다.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--host</code></td>
      <td>옵션</td>
      <td>서버가 요청을 받을 네트워크 주소를 지정한다.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">127.0.0.1</code></td>
      <td><code class="language-plaintext highlighter-rouge">--host</code>의 인자</td>
      <td>현재 컴퓨터에서 들어오는 요청만 허용한다.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--port</code></td>
      <td>옵션</td>
      <td>서버가 사용할 포트를 지정한다.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">8010</code></td>
      <td><code class="language-plaintext highlighter-rouge">--port</code>의 인자</td>
      <td>서버가 8010번 포트에서 요청을 받도록 한다.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--reload</code></td>
      <td>옵션</td>
      <td>Python 파일이 변경되면 서버를 자동으로 재시작한다. 개발 환경에서 사용한다.</td>
    </tr>
  </tbody>
</table>

<p>실행 흐름은 다음과 같다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Uvicorn 실행
    ↓
app/main.py 모듈 불러오기
    ↓
main.py의 app 객체 찾기
    ↓
127.0.0.1:8010에서 서버 실행
    ↓
코드 변경 시 자동 재시작
</code></pre></div></div>

<p>실행 후 Swagger 문서는 다음 주소로 접속한다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://localhost:8010/docs
</code></pre></div></div>

<p>127.0.0.1은 외부 컴퓨터나 Docker 컨테이너 밖에서 접근하기 어렵다. Docker 컨테이너에서 실행한다면 일반적으로 다음처럼 0.0.0.0을 사용한다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uvicorn app.main:app --host 0.0.0.0 --port 8010
</code></pre></div></div>

<p>–reload는 개발 편의 기능이므로 운영 환경에서는 일반적으로 사용하지 않는다.</p>

<h2 id="6-2-test-apips1-소스-분석">6-2. test-api.ps1 소스 분석</h2>

<pre><code class="language-Powershell">
# PowerShell 명령 실행 중 오류가 발생하면 스크립트를 즉시 중단한다.
$ErrorActionPreference = "Stop"


# ------------------------------------------------------------
# 1. Health Check API 테스트
# ------------------------------------------------------------

# 현재 실행할 테스트의 이름을 화면에 출력한다.
Write-Host "Health check"

# FastAPI 서버의 상태 확인 API를 GET 방식으로 호출한다.
# 서버가 정상적으로 실행 중이면 {"status": "ok"} 응답을 받는다.
Invoke-RestMethod `
    -Uri "http://localhost:8010/health" `
    -Method Get


# ------------------------------------------------------------
# 2. Simple Agent 테스트
# ------------------------------------------------------------

# `n은 줄바꿈을 의미한다.
# 한 줄을 띄운 후 테스트 이름을 화면에 출력한다.
Write-Host "`nSimple Agent"

# Agent에게 전달할 사용자 메시지를 PowerShell 해시 테이블로 작성한다.
# ConvertTo-Json은 해시 테이블을 JSON 문자열로 변환한다.
$body = @{
    message = "Docker image가 무엇인지 초보자에게 한 문장으로 설명해줘."
} | ConvertTo-Json

# simple Agent API를 POST 방식으로 호출한다.
#
# -Uri는 요청을 보낼 API 주소이다.
# -Method Post는 HTTP POST 방식을 사용한다.
# -ContentType은 요청 본문의 형식이 JSON임을 나타낸다.
# -Body는 앞에서 만든 JSON 메시지를 전달한다.
Invoke-RestMethod `
    -Uri "http://localhost:8010/agents/simple" `
    -Method Post `
    -ContentType "application/json" `
    -Body $body


# ------------------------------------------------------------
# 3. Function Tool Agent 테스트
# ------------------------------------------------------------

# 한 줄을 띄운 후 Tool Agent 테스트 이름을 출력한다.
Write-Host "`nTool Agent"

# 상품 가격과 수량 계산을 요청하는 메시지를 JSON으로 변환한다.
$body = @{
    message = "가격이 12500원인 상품 3개의 총액을 계산해줘."
} | ConvertTo-Json

# tool Agent API를 호출한다.
# tool Agent는 계산이 필요하다고 판단하면 calculate_total 도구를 호출한다.
Invoke-RestMethod `
    -Uri "http://localhost:8010/agents/tool" `
    -Method Post `
    -ContentType "application/json" `
    -Body $body


# ------------------------------------------------------------
# 4. Triage 및 Handoff Agent 테스트
# ------------------------------------------------------------

# 한 줄을 띄운 후 Triage/Handoff Agent 테스트 이름을 출력한다.
Write-Host "`nTriage/Handoff Agent"

# 사내 Kubernetes 배포에 관한 사용자 메시지를 JSON으로 변환한다.
$body = @{
    message = "이 컨테이너를 사내 Kubernetes에 배포하려면 무엇을 준비해야 하나?"
} | ConvertTo-Json

# triage Agent API를 호출한다.
#
# triage Agent는 요청 내용을 분석한다.
# 이 요청은 사내 환경에 관한 질문이므로 on_prem_specialist로
# handoff할 가능성이 높다.
Invoke-RestMethod `
    -Uri "http://localhost:8010/agents/triage" `
    -Method Post `
    -ContentType "application/json" `
    -Body $body
</code></pre>]]></content><author><name>{&quot;name&quot;=&gt;nil, &quot;avatar&quot;=&gt;nil, &quot;bio&quot;=&gt;&quot;서진호&quot;, &quot;location&quot;=&gt;&quot;Seoul. South Korea&quot;, &quot;email&quot;=&gt;&quot;synabreu@outlook.com&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Email&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-envelope-square&quot;, &quot;url&quot;=&gt;&quot;mailto:synabreu@outlook.com&quot;}, {&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://synabreu.github.io&quot;}, {&quot;label&quot;=&gt;&quot;Twitter&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-twitter-square&quot;}, {&quot;label&quot;=&gt;&quot;Facebook&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-facebook-square&quot;, &quot;url&quot;=&gt;&quot;https://www.facebook.com/jinho.seo.165&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/synabreu/&quot;}, {&quot;label&quot;=&gt;&quot;Instagram&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-instagram&quot;}]}</name><email>synabreu@outlook.com</email></author><category term="openai" /><category term="오픈AI" /><category term="OpenAI" /><category term="GPT-5.6" /><category term="agenticai" /><category term="aiagent" /><category term="openrouter" /><category term="docker" /><category term="powershell" /><category term="fastapi" /><category term="swagger-ui" /><category term="visual studio code" /><category term="Linux" /><category term="Windows11" /><category term="Kubernetes" /><category term="On-Premises" /><summary type="html"><![CDATA[이제 FastAPI로 서비스화는 방법에 대해 설명하고 윈도우 환경에서 실행하는 run-native 파워셀 파일과 API를 테스트하는 test-api 파워셀 파일을 각각 내부에 어떻게 구성되어 있는지 알아보고 실행해 보자!]]></summary></entry></feed>