<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rihpig</title>
    <description>The latest articles on DEV Community by Rihpig (@rihpig).</description>
    <link>https://dev.to/rihpig</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2745155%2Ff727be57-3d98-4e9b-90ef-46211b492018.jpg</url>
      <title>DEV Community: Rihpig</title>
      <link>https://dev.to/rihpig</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rihpig"/>
    <language>en</language>
    <item>
      <title>AWS Strands Agents란? 오픈 소스 모델 기반 에이전트 SDK</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Fri, 26 Jun 2026 08:27:21 +0000</pubDate>
      <link>https://dev.to/rihpig/aws-strands-agentsran-opeun-soseu-model-giban-eijeonteu-sdk-1jn1</link>
      <guid>https://dev.to/rihpig/aws-strands-agentsran-opeun-soseu-model-giban-eijeonteu-sdk-1jn1</guid>
      <description>&lt;p&gt;거대한 &lt;code&gt;if/else&lt;/code&gt; 상태 머신으로 AI 에이전트를 만들면 기능이 늘어날수록 라우팅, 예외 처리, 재시도 로직이 빠르게 취약해집니다. Strands Agents는 반대로 접근합니다. 사용자는 시스템 프롬프트와 도구 목록을 제공하고, 모델이 계획 수립과 도구 호출 순서를 결정하게 합니다. Strands Agents는 AWS의 오픈소스 SDK로, 2025년 5월 &lt;a href="https://github.com/strands-agents/sdk-python" rel="noopener noreferrer"&gt;Apache License 2.0&lt;/a&gt;에 따라 공개되었으며 Amazon Q Developer 및 AWS Glue와 같은 Amazon 팀의 프로덕션 에이전트에도 사용됩니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;지금 Apidog를 사용해 보세요&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Strands Agents의 실제 동작 방식
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://strandsagents.com/" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt;는 몇 줄의 코드로 AI 에이전트를 만들고 실행하기 위한 SDK입니다. 기본 구성은 단순합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;사용할 모델을 선택합니다.&lt;/li&gt;
&lt;li&gt;에이전트의 역할을 시스템 프롬프트로 정의합니다.&lt;/li&gt;
&lt;li&gt;모델이 호출할 수 있는 도구를 등록합니다.&lt;/li&gt;
&lt;li&gt;에이전트를 함수처럼 호출합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;실행 시 모델은 프롬프트를 읽고, 필요한 도구를 선택하고, 도구 실행 결과를 확인한 뒤, 작업이 끝났다고 판단할 때까지 이 루프를 반복합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-473.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-473.png" alt="" width="799" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Strands는 Python과 TypeScript를 지원합니다. 이름은 에이전트를 구성하는 핵심 요소인 모델과 도구를 가리킵니다. AWS가 내부에서 운영한 뒤 오픈소스로 공개했기 때문에, 설계는 단순 데모보다 프로덕션 요구 사항에 가깝습니다. 미리보기 출시 이후 PyPI 다운로드 수가 15만 회를 넘었고, 다중 에이전트 기본 요소와 에이전트 간 A2A 프로토콜 지원을 포함한 1.0 릴리스에 도달했습니다.&lt;/p&gt;

&lt;p&gt;이미 다른 에이전트 SDK를 검토했다면 Strands의 위치는 익숙할 수 있습니다. Strands는 &lt;a href="https://apidog.com/kr/blog/langgraph?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;LangGraph&lt;/a&gt; 및 &lt;a href="https://apidog.com/kr/blog/google-adk?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Google ADK&lt;/a&gt;와 같은 범주에 있지만, 사용자가 직접 그래프를 설계하기보다 모델의 추론 루프에 더 많은 제어권을 줍니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  모델 중심 방식과 하드코딩된 오케스트레이션 비교
&lt;/h2&gt;

&lt;p&gt;초기 에이전트 프레임워크는 보통 워크플로우를 먼저 정의하도록 요구했습니다. 노드, 엣지, 조건, 라우팅 규칙을 작성하고, 모델은 그 안에서 선택을 수행합니다.&lt;/p&gt;

&lt;p&gt;Strands는 이 책임을 모델 쪽으로 이동합니다. 최신 모델은 목표를 이해하고, 중간 단계를 계획하고, 도구를 호출하고, 결과를 바탕으로 다음 행동을 정할 수 있습니다. 따라서 개발자는 모든 분기를 코드로 인코딩하는 대신 다음 두 가지에 집중합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;좋은 시스템 프롬프트 작성&lt;/li&gt;
&lt;li&gt;모델이 사용할 수 있는 도구 제공&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;접근 방식&lt;/th&gt;
&lt;th&gt;개발자가 정의하는 것&lt;/th&gt;
&lt;th&gt;제어 흐름 위치&lt;/th&gt;
&lt;th&gt;새 기능 추가 비용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;하드코딩된 오케스트레이션&lt;/td&gt;
&lt;td&gt;노드, 엣지, 조건, 라우팅&lt;/td&gt;
&lt;td&gt;사용자 그래프 코드&lt;/td&gt;
&lt;td&gt;그래프 수정, 경로 재테스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모델 중심 방식, Strands&lt;/td&gt;
&lt;td&gt;프롬프트, 도구 목록&lt;/td&gt;
&lt;td&gt;모델의 추론 루프&lt;/td&gt;
&lt;td&gt;도구 추가, 프롬프트 업데이트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;트레이드오프는 명확합니다. 모델 중심 에이전트는 빠르게 만들고 변경하기 쉽지만, 완전한 결정론은 줄어듭니다. 항상 동일한 순서로 실행되어야 하는 워크플로우라면 훅, 다중 에이전트 구조, 또는 그래프 기반 프레임워크가 더 적합할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  최소 Strands 에이전트 만들기
&lt;/h2&gt;

&lt;p&gt;가장 작은 Strands 프로그램은 &lt;code&gt;Agent&lt;/code&gt;와 &lt;code&gt;@tool&lt;/code&gt;만으로 시작할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;word_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Count the words in a block of text.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a concise writing assistant.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word_count&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How many words are in this sentence?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;여기서 핵심은 &lt;code&gt;@tool&lt;/code&gt; 데코레이터입니다. 일반 Python 함수를 모델이 호출 가능한 도구로 변환합니다.&lt;/p&gt;

&lt;p&gt;실무에서 확인해야 할 부분은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;함수 이름은 모델이 이해하기 쉬워야 합니다.&lt;/li&gt;
&lt;li&gt;독스트링은 도구의 목적을 명확히 설명해야 합니다.&lt;/li&gt;
&lt;li&gt;타입 힌트는 입력 스키마로 사용되므로 가능한 한 정확해야 합니다.&lt;/li&gt;
&lt;li&gt;도구는 예측 가능한 응답 형태를 반환해야 합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예를 들어 외부 API를 호출하는 도구라면 다음처럼 실패 케이스를 명시적으로 반환하는 편이 디버깅에 유리합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch the current status for a user by user_id.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com/users/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 하면 모델이 도구 실패를 일반 예외가 아니라 처리 가능한 결과로 볼 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  모델 제공업체 선택하기
&lt;/h2&gt;

&lt;p&gt;Strands는 모델 제공업체에 유연합니다. 기본 제공업체는 Amazon Bedrock이며, 기본적으로 에이전트는 &lt;code&gt;us-west-2&lt;/code&gt; 리전의 Claude Sonnet 모델을 사용합니다. 정확한 기본 모델 ID는 SDK 버전에 따라 달라질 수 있으므로 코드에 하드코딩하기보다 설치된 SDK 문서를 확인하는 것이 좋습니다.&lt;/p&gt;

&lt;p&gt;지원 가능한 모델 경로는 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;도구 사용 및 스트리밍을 지원하는 Amazon Bedrock 모델&lt;/li&gt;
&lt;li&gt;Anthropic API를 통한 Claude 제품군&lt;/li&gt;
&lt;li&gt;Llama API를 통한 Llama 모델&lt;/li&gt;
&lt;li&gt;로컬 개발용 Ollama&lt;/li&gt;
&lt;li&gt;LiteLLM을 통한 OpenAI 등 기타 제공업체&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;실무에서는 다음 흐름이 유용합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;로컬에서는 Ollama 또는 개발용 모델로 빠르게 반복합니다.&lt;/li&gt;
&lt;li&gt;도구 스키마와 프롬프트를 안정화합니다.&lt;/li&gt;
&lt;li&gt;프로덕션에서는 Bedrock 또는 관리형 제공업체로 전환합니다.&lt;/li&gt;
&lt;li&gt;에이전트 코드와 도구 코드는 최대한 그대로 유지합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;제공업체 전환은 보통 모델 객체 변경에 가깝고, 에이전트 루프나 도구 자체를 다시 작성하는 작업은 아닙니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  도구 설계 체크리스트
&lt;/h2&gt;

&lt;p&gt;Strands에서 도구는 에이전트가 외부 세계와 상호작용하는 인터페이스입니다. 도구는 다음 중 하나일 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;직접 작성한 Python 함수&lt;/li&gt;
&lt;li&gt;커뮤니티 패키지에서 제공하는 도구&lt;/li&gt;
&lt;li&gt;MCP 서버를 통해 노출된 도구&lt;/li&gt;
&lt;li&gt;REST API를 감싼 래퍼 함수&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;도구를 작성할 때는 다음 기준을 적용하세요.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Search recent orders for a customer.

    Args:
        customer_id: The customer identifier.
        limit: Maximum number of orders to return.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;권장 사항:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;입력 파라미터를 작게 유지합니다.&lt;/li&gt;
&lt;li&gt;반환값은 JSON 직렬화 가능한 구조로 유지합니다.&lt;/li&gt;
&lt;li&gt;성공과 실패 응답의 형태를 일관되게 만듭니다.&lt;/li&gt;
&lt;li&gt;외부 API 호출에는 timeout을 설정합니다.&lt;/li&gt;
&lt;li&gt;인증 정보는 코드에 직접 넣지 말고 환경 변수나 시크릿 관리자를 사용합니다.&lt;/li&gt;
&lt;li&gt;모델이 오해하지 않도록 독스트링에 도구의 목적과 제한을 적습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  다중 에이전트와 MCP 사용하기
&lt;/h2&gt;

&lt;p&gt;단일 에이전트로도 많은 작업을 처리할 수 있지만, 실제 서비스에서는 역할 분리가 필요할 수 있습니다. Strands 1.0은 다중 에이전트 애플리케이션을 위한 기본 요소를 제공합니다.&lt;/p&gt;

&lt;p&gt;대표적인 패턴은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent-as-Tool&lt;/strong&gt;: 한 에이전트를 다른 에이전트가 호출 가능한 도구처럼 사용합니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swarm 스타일 조정&lt;/strong&gt;: 여러 에이전트가 하나의 문제를 함께 해결합니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A2A 프로토콜&lt;/strong&gt;: Strands 에이전트가 다른 프레임워크로 만든 에이전트와 통신할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예를 들어 고객 지원 시스템에서는 다음처럼 역할을 나눌 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;라우터 에이전트: 사용자 요청을 분류&lt;/li&gt;
&lt;li&gt;주문 조회 에이전트: 주문 API 호출&lt;/li&gt;
&lt;li&gt;환불 정책 에이전트: 정책 문서 검색&lt;/li&gt;
&lt;li&gt;응답 작성 에이전트: 최종 답변 정리&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MCP도 Strands에서 중요한 통합 방식입니다. &lt;a href="https://apidog.com/kr/blog/what-is-mcp-model-context-protocol?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;모델 컨텍스트 프로토콜, MCP&lt;/a&gt;는 모델을 도구 및 데이터 소스에 연결하기 위한 개방형 표준입니다. Strands는 게시된 MCP 서버에 연결하고, 해당 서버의 도구를 에이전트에 전달할 수 있습니다.&lt;/p&gt;

&lt;p&gt;이미 MCP 서버를 운영 중이라면 새 기능을 에이전트에 붙이는 가장 빠른 방법이 될 수 있습니다. 단, MCP 서버의 응답 안정성에 에이전트 품질이 직접 의존하므로 엔드포인트 테스트와 모의 응답 검증이 중요합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strands 에이전트 배포하기
&lt;/h2&gt;

&lt;p&gt;Strands는 노트북이나 로컬 스크립트에서 시작해 프로덕션 환경으로 이동할 수 있도록 설계되었습니다. 에이전트는 일반 Python 또는 TypeScript 애플리케이션이므로 기존 배포 방식을 그대로 적용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;배포 대상 예시는 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;관리형 에이전트 런타임을 위한 Amazon Bedrock AgentCore&lt;/li&gt;
&lt;li&gt;이벤트 기반 단기 작업을 위한 AWS Lambda&lt;/li&gt;
&lt;li&gt;컨테이너화된 장기 실행 서비스를 위한 AWS Fargate 또는 Amazon EKS&lt;/li&gt;
&lt;li&gt;컨테이너를 실행할 수 있는 일반 Docker 환경&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;배포 전에는 최소한 다음 항목을 확인하세요.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 예시: 환경 변수 확인&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$AWS_REGION&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$MODEL_PROVIDER&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$TOOL_API_BASE_URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;운영 환경에서는 다음을 분리하는 것이 좋습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;모델 설정&lt;/li&gt;
&lt;li&gt;도구 API의 base URL&lt;/li&gt;
&lt;li&gt;인증 토큰&lt;/li&gt;
&lt;li&gt;timeout 및 retry 정책&lt;/li&gt;
&lt;li&gt;로깅 레벨&lt;/li&gt;
&lt;li&gt;관찰성 설정&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS는 관찰성 훅도 문서화하고 있으므로, 운영 중에는 다음 정보를 추적해야 합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;모델이 선택한 도구&lt;/li&gt;
&lt;li&gt;도구 호출 입력값&lt;/li&gt;
&lt;li&gt;도구 응답 상태&lt;/li&gt;
&lt;li&gt;실패한 외부 API&lt;/li&gt;
&lt;li&gt;최종 응답 생성까지 걸린 시간&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Apidog로 Strands 에이전트의 API 의존성 테스트하기
&lt;/h2&gt;

&lt;p&gt;Strands는 에이전트를 구축하는 SDK입니다. 하지만 에이전트가 호출하는 API 자체를 검증해 주지는 않습니다. 실제 Strands 에이전트는 보통 두 종류의 HTTP 엔드포인트에 의존합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;모델 뒤에 있는 LLM 제공업체 API&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@tool&lt;/code&gt; 함수 또는 MCP 서버 뒤에 있는 REST API 및 도구 API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이 엔드포인트가 불안정하면 에이전트는 모델 문제처럼 보이는 방식으로 실패할 수 있습니다. 실제 원인은 잘못된 상태 코드, 변경된 응답 필드, 인증 실패, timeout일 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-473.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-473.png" alt="" width="799" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;는 에이전트가 실제 API에 접근하기 전에 엔드포인트를 테스트하고 모의할 수 있는 작업 공간입니다.&lt;/p&gt;

&lt;p&gt;실무에서 사용할 수 있는 패턴은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;모델 또는 도구 엔드포인트 모의&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
개발 루프에서 매번 실제 모델 호출 비용을 쓰거나 rate limit에 걸리지 않도록 합니다. 관련 패턴은 &lt;a href="https://apidog.com/kr/blog/ai-agent-apidog-test-harness?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog로 AI 에이전트 테스트 하네스 구축&lt;/a&gt; 문서에서 확인할 수 있습니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;도구 응답 형태 검증&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
도구가 잘못된 payload를 반환하면 프로덕션이 아니라 테스트 단계에서 발견해야 합니다. 필드, 타입, 상태 코드 검증은 &lt;a href="https://apidog.com/kr/blog/api-assertions?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 단언 가이드&lt;/a&gt;를 참고하세요.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;모의 API 구성&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
실제 서비스와 유사한 정상 응답, 오류 응답, 빈 결과, 인증 실패 케이스를 만들어 에이전트가 어떻게 반응하는지 테스트합니다. 자세한 내용은 &lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;모의 API&lt;/a&gt;를 참고하세요.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;환경별 API 키 관리&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
개발, 스테이징, 프로덕션 에이전트가 서로 다른 백엔드에 안전하게 인증하도록 구성합니다. 인증 정보를 코드에 직접 넣지 않는 것이 핵심입니다.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예를 들어 Strands 도구가 다음 API에 의존한다고 가정합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get invoice details by invoice_id.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apidog에서는 다음 케이스를 먼저 정의할 수 있습니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;케이스&lt;/th&gt;
&lt;th&gt;예상 상태&lt;/th&gt;
&lt;th&gt;에이전트가 처리해야 할 동작&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;정상 invoice 조회&lt;/td&gt;
&lt;td&gt;&lt;code&gt;200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;금액, 상태, 만기일을 사용자에게 요약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;존재하지 않는 invoice&lt;/td&gt;
&lt;td&gt;&lt;code&gt;404&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;찾을 수 없다고 명확히 응답&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인증 실패&lt;/td&gt;
&lt;td&gt;&lt;code&gt;401&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;내부 인증 문제로 안내하거나 재시도 중단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버 오류&lt;/td&gt;
&lt;td&gt;&lt;code&gt;500&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;일시적 실패로 처리하고 안전하게 종료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;분명히 말하면 Apidog는 에이전트 프레임워크가 아니며 오케스트레이션을 수행하지 않습니다. Strands가 에이전트의 두뇌라면, Apidog는 그 아래 API 배관을 검증하는 작업대입니다. &lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 다운로드&lt;/a&gt;해 도구 엔드포인트에 대한 모의를 빠르게 설정할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strands Agents를 사용할 시점
&lt;/h2&gt;

&lt;p&gt;Strands는 다음 상황에 잘 맞습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;모델의 계획 능력을 활용해 빠르게 에이전트를 만들고 싶을 때&lt;/li&gt;
&lt;li&gt;AWS와 Bedrock을 이미 사용 중일 때&lt;/li&gt;
&lt;li&gt;단일 에이전트로 시작해 나중에 다중 에이전트로 확장하고 싶을 때&lt;/li&gt;
&lt;li&gt;MCP 도구를 별도 통합 코드 없이 사용하고 싶을 때&lt;/li&gt;
&lt;li&gt;도구 목록과 프롬프트를 중심으로 에이전트를 반복 개선하고 싶을 때&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;반대로 다음 상황에서는 신중해야 합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;모든 분기가 사전에 정의되어야 할 때&lt;/li&gt;
&lt;li&gt;감사 가능한 결정론적 실행 흐름이 필수일 때&lt;/li&gt;
&lt;li&gt;모델이 제어 흐름을 선택하면 안 되는 규제 환경일 때&lt;/li&gt;
&lt;li&gt;그래프 기반 워크플로우가 이미 명확하게 정의되어 있을 때&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strands에서도 훅과 다중 에이전트 구조로 제어를 추가할 수 있습니다. 하지만 기본 철학은 그래프 우선이 아니라 모델 중심입니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  구현 전 체크리스트
&lt;/h2&gt;

&lt;p&gt;Strands로 첫 에이전트를 만들기 전에 다음 항목을 정리하면 시행착오를 줄일 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] 에이전트가 해결할 작업을 한 문장으로 정의했는가?&lt;/li&gt;
&lt;li&gt;[ ] 시스템 프롬프트에 역할, 제한, 출력 형식을 명시했는가?&lt;/li&gt;
&lt;li&gt;[ ] 각 도구의 입력과 출력이 명확한가?&lt;/li&gt;
&lt;li&gt;[ ] 도구 함수에 타입 힌트와 독스트링이 있는가?&lt;/li&gt;
&lt;li&gt;[ ] 외부 API 호출에 timeout이 있는가?&lt;/li&gt;
&lt;li&gt;[ ] 실패 응답 형태가 일관적인가?&lt;/li&gt;
&lt;li&gt;[ ] 실제 API 대신 사용할 mock이 있는가?&lt;/li&gt;
&lt;li&gt;[ ] 개발, 스테이징, 프로덕션 환경 변수가 분리되어 있는가?&lt;/li&gt;
&lt;li&gt;[ ] 모델 호출과 도구 호출 로그를 추적할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Strands Agents는 무료이며 오픈소스인가요?
&lt;/h3&gt;

&lt;p&gt;네. Strands Agents는 GitHub에 소스가 공개된 Apache License 2.0 기반 오픈소스입니다. SDK 자체에는 라이선스 비용이 없습니다. 다만 모델 추론, Bedrock 사용, Lambda 실행, 컨테이너 인프라와 같은 클라우드 리소스 비용은 별도로 발생합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strands와 함께 Amazon Bedrock을 반드시 사용해야 하나요?
&lt;/h3&gt;

&lt;p&gt;아니요. Bedrock이 기본 제공업체이지만 Strands는 Anthropic API, Llama API, 로컬 실행용 Ollama, LiteLLM을 통한 기타 제공업체도 지원합니다. 모델 객체를 변경하고 나머지 에이전트 코드와 도구는 그대로 유지하는 방식으로 전환할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strands와 그래프 기반 프레임워크의 차이는 무엇인가요?
&lt;/h3&gt;

&lt;p&gt;Strands는 모델 중심입니다. 프롬프트와 도구를 제공하면 모델이 다음 단계를 결정합니다. 그래프 기반 프레임워크는 제어 흐름을 노드와 엣지로 정의하도록 요구합니다. Strands는 빠른 구축과 변경에 유리하고, 그래프 기반 방식은 더 엄격하고 예측 가능한 실행에 유리합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strands 에이전트가 의존하는 API는 어떻게 테스트하나요?
&lt;/h3&gt;

&lt;p&gt;에이전트와 독립적으로 테스트하는 것이 좋습니다. LLM 및 도구 엔드포인트를 모의하고, 응답 형태를 단언하며, CI에서 검사를 실행하세요. &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt; 같은 도구는 mock과 assertion을 처리할 수 있습니다. &lt;a href="https://apidog.com/kr/blog/how-to-test-chatgpt-api-with-apidog?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog로 ChatGPT API 테스트하기&lt;/a&gt; 가이드는 인증, 스트리밍, 도구 호출 테스트와 같은 에이전트 백엔드에 직접 연결되는 내용을 다룹니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  결론
&lt;/h2&gt;

&lt;p&gt;Strands Agents는 에이전트 구축을 단순화합니다. 모델, 시스템 프롬프트, 도구를 정의하면 모델이 추론 루프를 실행합니다. 단일 에이전트에서 다중 에이전트로 확장할 수 있고, MCP 및 A2A를 지원하며, AWS 스택 또는 일반 컨테이너 환경으로 배포할 수 있습니다.&lt;/p&gt;

&lt;p&gt;구현에서 중요한 부분은 에이전트 자체만이 아닙니다. 에이전트가 호출하는 API가 안정적이어야 합니다. 도구 엔드포인트를 모의하고, 응답 형태를 검증하고, 실패 케이스를 미리 테스트하면 모델 문제가 아닌 API 문제를 더 빨리 발견할 수 있습니다. 이 지점에서 Strands와 Apidog를 함께 사용하면 에이전트 개발 루프를 더 안전하게 만들 수 있습니다.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>세마틱 커널이란 무엇인가? 마이크로소프트 AI 오케스트레이션용 SDK</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Fri, 26 Jun 2026 08:21:43 +0000</pubDate>
      <link>https://dev.to/rihpig/sematig-keoneoliran-mueosinga-maikeurosopeuteu-ai-okeseuteureisyeonyong-sdk-ppp</link>
      <guid>https://dev.to/rihpig/sematig-keoneoliran-mueosinga-maikeurosopeuteu-ai-okeseuteureisyeonyong-sdk-ppp</guid>
      <description>&lt;p&gt;Microsoft 스택에서 AI 기능을 추가해야 하지만 별도의 Python 서비스를 운영하고 싶지 않다면, Semantic Kernel은 현실적인 선택지입니다. Semantic Kernel은 기존 코드와 API를 대규모 언어 모델에 연결하는 Microsoft의 오픈소스 SDK이며, C#, Python, Java에서 사용할 수 있습니다. 이 글에서는 Semantic Kernel의 핵심 구조, 커널과 플러그인의 동작 방식, 그리고 &lt;a href="https://apidog.com/kr/blog/openapi-specification?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenAPI 사양&lt;/a&gt;을 통해 REST API를 모델이 호출 가능한 도구로 바꾸는 방법을 구현 관점에서 정리합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;오늘 Apidog를 사용해 보세요&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantic Kernel이란 무엇인가
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/" rel="noopener noreferrer"&gt;Semantic Kernel&lt;/a&gt;은 Microsoft가 AI 에이전트를 구축하고 LLM을 애플리케이션 코드에 연결하기 위해 제공하는 경량 오픈소스 SDK입니다.&lt;/p&gt;

&lt;p&gt;실무적으로는 다음 역할을 합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;애플리케이션 코드와 LLM 사이에 위치합니다.&lt;/li&gt;
&lt;li&gt;모델이 사용할 수 있는 함수 목록을 제공합니다.&lt;/li&gt;
&lt;li&gt;모델이 함수를 호출하면 실제 코드를 실행합니다.&lt;/li&gt;
&lt;li&gt;실행 결과를 다시 모델에 전달합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;즉, 개발자는 일반적인 메서드나 API를 작성하고, Semantic Kernel은 이를 모델이 호출할 수 있는 도구로 노출합니다.&lt;/p&gt;

&lt;p&gt;Semantic Kernel이 특히 유용한 이유는 세 가지입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;C#/.NET, Python, Java 공식 지원&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Python 중심 에이전트 프레임워크와 달리 .NET 백엔드에서도 자연스럽게 사용할 수 있습니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;모델 공급자에 덜 종속됨&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
OpenAI, Azure OpenAI 등 커넥터를 통해 모델을 연결합니다. 모델을 교체할 때 애플리케이션 전체를 다시 작성하기보다 설정을 변경하는 방식에 가깝습니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;엔터프라이즈 환경을 고려한 구조&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
텔레메트리, 필터, 후크를 통해 AI 호출 흐름을 로깅하고 감사하거나 중간에 가로챌 수 있습니다.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  커널, 플러그인, 함수 구조
&lt;/h2&gt;

&lt;p&gt;Semantic Kernel의 핵심 객체는 &lt;code&gt;Kernel&lt;/code&gt;입니다. AI 기능을 위한 종속성 주입 컨테이너처럼 생각하면 됩니다.&lt;/p&gt;

&lt;p&gt;기본 흐름은 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;커널을 생성합니다.&lt;/li&gt;
&lt;li&gt;LLM 커넥터를 등록합니다.&lt;/li&gt;
&lt;li&gt;모델이 호출할 수 있는 플러그인을 등록합니다.&lt;/li&gt;
&lt;li&gt;프롬프트를 실행합니다.&lt;/li&gt;
&lt;li&gt;모델이 필요하다고 판단하면 플러그인의 함수를 호출합니다.&lt;/li&gt;
&lt;li&gt;함수 실행 결과를 다시 모델에 전달합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  플러그인과 함수
&lt;/h3&gt;

&lt;p&gt;플러그인은 모델에 노출할 함수들의 묶음입니다. 함수는 모델이 호출할 수 있는 단일 기능입니다.&lt;/p&gt;

&lt;p&gt;Semantic Kernel에서 함수는 크게 두 종류입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;네이티브 함수&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
C# 메서드나 Python 함수처럼 실제 코드로 작성된 함수입니다. 어노테이션이나 설명을 통해 모델이 이해할 수 있게 만듭니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;프롬프트 함수&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
텍스트 요약, 분류, 재작성처럼 모델 호출 자체를 템플릿화한 함수입니다.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  C#에서 Semantic Kernel 시작하기
&lt;/h2&gt;

&lt;p&gt;아래 예시는 커널을 만들고, OpenAI 채팅 모델을 등록한 뒤, &lt;code&gt;LightsPlugin&lt;/code&gt;을 모델이 호출할 수 있도록 추가하는 기본 구조입니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenAIChatCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"gpt-4o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddFromType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LightsPlugin&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"Lights"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokePromptAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"Turn the kitchen light blue"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이제 모델은 프롬프트를 해석하다가 조명 상태 변경이 필요하다고 판단하면 &lt;code&gt;LightsPlugin&lt;/code&gt; 안의 함수를 호출할 수 있습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 플러그인은 다음과 같은 형태가 될 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LightsPlugin&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;KernelFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"change_light_state"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Changes the state or color of a light"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ChangeLightState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The name of the light"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;lightName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The target color"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 실제 조명 API 호출 또는 내부 서비스 호출&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lightName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; light changed to &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;핵심은 함수명과 파라미터 설명입니다. 모델은 이 메타데이터를 보고 어떤 함수를 어떤 인자로 호출할지 결정합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAPI-투-플러그인 패턴
&lt;/h2&gt;

&lt;p&gt;Semantic Kernel의 실무 활용에서 가장 중요한 기능 중 하나는 OpenAPI 사양을 플러그인으로 가져오는 기능입니다.&lt;/p&gt;

&lt;p&gt;이미 REST API가 있고 OpenAPI 문서가 있다면, 별도의 래퍼 코드를 많이 작성하지 않아도 됩니다. Semantic Kernel이 OpenAPI 사양을 읽고 각 엔드포인트를 모델이 호출 가능한 함수로 변환합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  C#에서 OpenAPI 플러그인 가져오기
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ImportPluginFromOpenApiAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"lights"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/v1/swagger.json"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;executionParameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiFunctionExecutionParameters&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;EnablePayloadNamespacing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python에서는 &lt;code&gt;add_plugin_from_openapi&lt;/code&gt;를 사용하고, Java에도 동등한 임포터가 있습니다.&lt;/p&gt;

&lt;p&gt;Semantic Kernel은 내부적으로 다음 작업을 수행합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OpenAPI 사양을 파싱합니다.&lt;/li&gt;
&lt;li&gt;각 path와 operation을 함수로 변환합니다.&lt;/li&gt;
&lt;li&gt;파라미터 이름, 타입, 설명, 스키마를 추출합니다.&lt;/li&gt;
&lt;li&gt;해당 메타데이터를 모델에 전달합니다.&lt;/li&gt;
&lt;li&gt;모델이 함수를 선택하면 HTTP 요청을 생성합니다.&lt;/li&gt;
&lt;li&gt;인증 콜백을 적용하고 요청을 전송합니다.&lt;/li&gt;
&lt;li&gt;응답을 다시 모델에 전달합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Semantic Kernel은 OpenAPI 2.0 및 3.0을 지원하며, 가능한 경우 3.1 사양을 3.0으로 다운그레이드합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  모델이 잘 호출하는 OpenAPI 사양 작성법
&lt;/h2&gt;

&lt;p&gt;OpenAPI 사양은 사람만 읽는 문서가 아닙니다. Semantic Kernel에서는 모델이 읽는 도구 설명이 됩니다.&lt;/p&gt;

&lt;p&gt;따라서 사양 품질이 낮으면 모델의 함수 호출 품질도 떨어집니다.&lt;/p&gt;

&lt;p&gt;다음 항목을 우선 점검하세요.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. operationId를 명확하게 작성하기
&lt;/h3&gt;

&lt;p&gt;좋지 않은 예:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;update&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;더 나은 예:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;updateKitchenLightColor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;모델은 &lt;code&gt;operationId&lt;/code&gt;를 보고 함수의 목적을 추론합니다. 짧고 모호한 이름보다 행동과 대상을 포함한 이름이 좋습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 파라미터 설명 추가하기
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lightId&lt;/span&gt;
    &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The unique identifier of the light to update.&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;color&lt;/span&gt;
    &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The target color name, such as blue, red, or warm_white.&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;설명이 없으면 모델은 인자를 추측해야 합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 가능한 경우 enum 사용하기
&lt;/h3&gt;

&lt;p&gt;느슨한 문자열보다 enum이 더 안전합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;blue&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;red&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;warm_white&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cool_white&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. 엔드포인트 수를 과도하게 늘리지 않기
&lt;/h3&gt;

&lt;p&gt;모델에 너무 많은 도구를 한 번에 노출하면 선택 오류가 늘어날 수 있습니다. 에이전트가 실제로 필요한 API만 플러그인으로 가져오는 편이 좋습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  에이전트 및 플래닝
&lt;/h2&gt;

&lt;p&gt;Semantic Kernel은 초기에는 목표를 단계별로 분해하는 명시적 플래너 중심으로 시작했습니다. 이후 최신 모델의 함수 호출 성능이 좋아지면서, 프레임워크도 모델이 직접 어떤 함수를 어떤 순서로 호출할지 결정하는 방식으로 이동했습니다.&lt;/p&gt;

&lt;p&gt;현재 Semantic Kernel은 다음 기능도 제공합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;에이전트 패턴&lt;/li&gt;
&lt;li&gt;다중 에이전트 패턴&lt;/li&gt;
&lt;li&gt;세션 기반 상태 관리&lt;/li&gt;
&lt;li&gt;에이전트 루프&lt;/li&gt;
&lt;li&gt;외부 도구 연결을 위한 MCP 지원&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;다른 에이전트 SDK와 비교하면 다음과 같습니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;프레임워크&lt;/th&gt;
&lt;th&gt;주요 언어&lt;/th&gt;
&lt;th&gt;오케스트레이션 모델&lt;/th&gt;
&lt;th&gt;최적의 활용처&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Semantic Kernel&lt;/td&gt;
&lt;td&gt;C#/.NET, Python, Java&lt;/td&gt;
&lt;td&gt;함수 호출 + 에이전트&lt;/td&gt;
&lt;td&gt;.NET 및 엔터프라이즈 팀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apidog.com/kr/blog/langgraph?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;LangGraph&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Python, JS&lt;/td&gt;
&lt;td&gt;명시적 상태 그래프&lt;/td&gt;
&lt;td&gt;복잡하고 분기되는 에이전트 흐름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apidog.com/kr/blog/google-adk?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Google ADK&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;에이전트 + 도구 모델&lt;/td&gt;
&lt;td&gt;Google Cloud 및 Gemini 스택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apidog.com/kr/blog/how-to-use-openai-agents-sdk?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenAI Agents SDK&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Python, JS&lt;/td&gt;
&lt;td&gt;에이전트 + 핸드오프&lt;/td&gt;
&lt;td&gt;OpenAI 중심 앱&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;절대적으로 더 좋은 프레임워크는 없습니다. 선택 기준은 다음입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;현재 사용하는 언어&lt;/li&gt;
&lt;li&gt;모델 공급자&lt;/li&gt;
&lt;li&gt;실행 흐름을 얼마나 명시적으로 제어해야 하는지&lt;/li&gt;
&lt;li&gt;기존 API와 OpenAPI 사양의 품질&lt;/li&gt;
&lt;li&gt;엔터프라이즈 감사 및 로깅 요구사항&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Semantic Kernel과 Microsoft Agent Framework의 관계
&lt;/h2&gt;

&lt;p&gt;Microsoft는 Microsoft Agent Framework, 즉 MAF를 도입했습니다. 문서에서는 이를 Semantic Kernel과 AutoGen의 직접적인 후속작으로 설명하며, 동일한 팀이 구축했다고 설명합니다.&lt;/p&gt;

&lt;p&gt;MAF는 다음 방향을 가집니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AutoGen의 에이전트 추상화&lt;/li&gt;
&lt;li&gt;Semantic Kernel의 엔터프라이즈 기능&lt;/li&gt;
&lt;li&gt;다중 에이전트 오케스트레이션을 위한 그래프 기반 워크플로우&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;실무적으로는 다음처럼 판단하면 됩니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;이미 Semantic Kernel로 만든 애플리케이션이 있다면 계속 사용해도 됩니다. SK는 안정적이고 지원됩니다.&lt;/li&gt;
&lt;li&gt;새로운 에이전트 프로젝트라면 Microsoft Agent Framework 문서를 함께 검토하세요.&lt;/li&gt;
&lt;li&gt;OpenAPI 사양을 통해 REST API를 에이전트 도구로 노출하는 패턴은 두 프레임워크 모두에서 중요한 방향입니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;즉, Semantic Kernel은 여전히 프로덕션에서 사용할 수 있는 안정적인 선택지입니다. 다만 Microsoft의 최신 에이전트 투자는 MAF 쪽으로 이동하고 있다는 점은 고려해야 합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantic Kernel을 사용해야 할 때
&lt;/h2&gt;

&lt;p&gt;다음 조건에 해당하면 Semantic Kernel을 검토할 만합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET 또는 Java 기반 백엔드에서 AI 오케스트레이션을 구현해야 한다.&lt;/li&gt;
&lt;li&gt;Python 사이드카 서비스를 추가하고 싶지 않다.&lt;/li&gt;
&lt;li&gt;기존 REST API를 OpenAPI 사양으로 관리하고 있다.&lt;/li&gt;
&lt;li&gt;모델이 REST API를 도구처럼 호출하게 만들고 싶다.&lt;/li&gt;
&lt;li&gt;텔레메트리, 필터, 후크, 감사 로그가 필요하다.&lt;/li&gt;
&lt;li&gt;OpenAI, Azure OpenAI 등 모델 공급자를 교체할 가능성이 있다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;반대로 다음 경우에는 다른 선택지도 검토하세요.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;팀이 Python 중심이고 최신 다중 에이전트 기능이 최우선이다.&lt;/li&gt;
&lt;li&gt;복잡한 상태 그래프와 분기 흐름을 명시적으로 제어해야 한다.&lt;/li&gt;
&lt;li&gt;Microsoft의 최신 에이전트 방향을 바로 따라가고 싶다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 경우 MAF 또는 그래프 우선 에이전트 프레임워크가 더 적합할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantic Kernel 에이전트 뒤의 API 테스트
&lt;/h2&gt;

&lt;p&gt;Semantic Kernel은 API를 대체하지 않습니다. API를 호출합니다.&lt;/p&gt;

&lt;p&gt;따라서 에이전트 품질은 다음 두 가지에 크게 의존합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;LLM 엔드포인트가 안정적으로 동작하는가&lt;/li&gt;
&lt;li&gt;OpenAPI 플러그인으로 가져온 REST API가 정확하게 설계되고 테스트되었는가&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;여기서 &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt; 같은 API 도구가 유용합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. OpenAPI 사양을 먼저 검증하기
&lt;/h3&gt;

&lt;p&gt;Semantic Kernel은 OpenAPI의 각 operation을 함수로 바꿉니다. 따라서 사양이 모호하면 모델 호출도 모호해집니다.&lt;/p&gt;

&lt;p&gt;가져오기 전에 다음을 확인하세요.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;모든 엔드포인트가 실제로 호출 가능한가&lt;/li&gt;
&lt;li&gt;요청 파라미터 설명이 충분한가&lt;/li&gt;
&lt;li&gt;응답이 스키마와 일치하는가&lt;/li&gt;
&lt;li&gt;인증 방식이 명확한가&lt;/li&gt;
&lt;li&gt;불필요한 엔드포인트가 플러그인에 포함되어 있지 않은가&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. 개발 중에는 Mock API 사용하기
&lt;/h3&gt;

&lt;p&gt;백엔드가 아직 완성되지 않았거나 LLM 호출 비용을 줄이고 싶다면 Mock API를 사용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 아직 구현되지 않은 조명 API를 다음처럼 모의할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lightId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kitchen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"on"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;관련 내용은 &lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;모의 API&lt;/a&gt;와 &lt;a href="https://apidog.com/kr/blog/how-to-mock-api-calls?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 호출 모의 방법&lt;/a&gt;을 참고할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 응답 형태를 단언하기
&lt;/h3&gt;

&lt;p&gt;에이전트는 API 응답 구조에 민감합니다. 백엔드 응답 필드가 바뀌면 모델의 후속 추론이 달라질 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/kr/blog/api-assertions?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 단언&lt;/a&gt;을 사용해 다음을 확인하세요.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;필수 필드가 존재하는가&lt;/li&gt;
&lt;li&gt;타입이 스키마와 일치하는가&lt;/li&gt;
&lt;li&gt;에러 응답 형식이 일관적인가&lt;/li&gt;
&lt;li&gt;모델이 기대하는 데이터 구조를 유지하는가&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. 환경별 키 분리하기
&lt;/h3&gt;

&lt;p&gt;LLM API 키와 내부 API 자격 증명을 코드에 하드코딩하지 마세요.&lt;/p&gt;

&lt;p&gt;최소한 다음 환경을 분리하는 것이 좋습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;개발&lt;/li&gt;
&lt;li&gt;스테이징&lt;/li&gt;
&lt;li&gt;프로덕션&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Semantic Kernel 설정에서도 환경별로 모델 엔드포인트, API 키, 플러그인 URL을 분리해 관리하는 것이 안전합니다.&lt;/p&gt;

&lt;p&gt;자세한 하네스 구성은 &lt;a href="https://apidog.com/kr/blog/ai-agent-apidog-test-harness?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog로 에이전트의 도구 호출 테스트하기&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Semantic Kernel은 무료이며 오픈소스인가요?
&lt;/h3&gt;

&lt;p&gt;네. Semantic Kernel은 Microsoft가 GitHub에 공개한 오픈소스 SDK입니다. C#/.NET, Python, Java SDK를 제공합니다.&lt;/p&gt;

&lt;p&gt;Semantic Kernel 자체는 무료로 사용할 수 있지만, OpenAI, Azure OpenAI 같은 모델 사용 비용은 별도로 발생합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Semantic Kernel은 어떤 언어를 지원하나요?
&lt;/h3&gt;

&lt;p&gt;C#/.NET, Python, Java를 지원합니다. 세 언어 모두 1.0+ 안정성을 보장합니다.&lt;/p&gt;

&lt;p&gt;C# SDK가 가장 성숙하지만, Python과 Java SDK도 커널, 플러그인, OpenAPI 가져오기 같은 핵심 기능을 제공합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Semantic Kernel은 OpenAPI 사양을 어떻게 사용하나요?
&lt;/h3&gt;

&lt;p&gt;C#에서는 &lt;code&gt;ImportPluginFromOpenApiAsync&lt;/code&gt;, Python에서는 &lt;code&gt;add_plugin_from_openapi&lt;/code&gt;를 사용해 OpenAPI 사양을 가져올 수 있습니다.&lt;/p&gt;

&lt;p&gt;Semantic Kernel은 사양을 파싱하고 각 operation을 호출 가능한 함수로 변환합니다. 모델은 operation 설명, 파라미터 설명, 스키마 정보를 보고 어떤 함수를 호출할지 결정합니다.&lt;/p&gt;

&lt;p&gt;따라서 사양을 먼저 검증하는 것이 중요합니다. &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;를 사용하면 OpenAPI 사양 검증과 실시간 엔드포인트 테스트를 함께 수행할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Semantic Kernel을 사용해야 하나요, 아니면 Microsoft Agent Framework를 사용해야 하나요?
&lt;/h3&gt;

&lt;p&gt;이미 Semantic Kernel 애플리케이션을 운영 중이라면 계속 사용해도 됩니다. Semantic Kernel은 안정적이고 지원됩니다.&lt;/p&gt;

&lt;p&gt;새 프로젝트라면 Microsoft Agent Framework 문서를 함께 검토하는 것이 좋습니다. Microsoft는 MAF를 Semantic Kernel과 AutoGen의 후속 방향으로 제시하고 있습니다.&lt;/p&gt;

&lt;p&gt;어떤 프레임워크를 선택하든, 에이전트가 호출하는 API를 테스트해야 합니다. 관련 예시는 &lt;a href="https://apidog.com/kr/blog/how-to-test-chatgpt-api-with-apidog?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog로 ChatGPT API를 테스트하는 방법&lt;/a&gt;을 참고하세요.&lt;/p&gt;

&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;Semantic Kernel은 Microsoft 스택에서 AI 기능을 구현하는 실용적인 방법입니다. 커널은 모델과 코드를 연결하고, 플러그인은 모델이 호출할 수 있는 기능을 제공하며, OpenAPI 가져오기는 기존 REST API를 에이전트 도구로 노출합니다.&lt;/p&gt;

&lt;p&gt;이미 .NET, Java, 또는 엔터프라이즈 API 기반 시스템을 운영하고 있다면 Semantic Kernel은 여전히 안정적인 선택입니다. 다만 새 프로젝트에서는 Microsoft Agent Framework의 방향도 함께 확인하는 것이 좋습니다.&lt;/p&gt;

&lt;p&gt;무엇을 선택하든 에이전트가 호출하는 API 계약은 견고해야 합니다. 에이전트에 연결하기 전에 사양을 설계하고, Mock API로 검증하고, 응답을 테스트하려면 &lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 다운로드&lt;/a&gt;해 API 계약을 먼저 정리하세요.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dotnet</category>
      <category>llm</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>PydanticAI란 무엇인가? 타입 안전 파이썬 에이전트 프레임워크 완벽 가이드</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Fri, 26 Jun 2026 08:19:38 +0000</pubDate>
      <link>https://dev.to/rihpig/pydanticairan-mueosinga-taib-anjeon-paisseon-eijeonteu-peureimweokeu-wanbyeog-gaideu-bmd</link>
      <guid>https://dev.to/rihpig/pydanticairan-mueosinga-taib-anjeon-paisseon-eijeonteu-peureimweokeu-wanbyeog-gaideu-bmd</guid>
      <description>&lt;p&gt;LLM 기능을 프로덕션에 배포한 뒤 잘못된 형식의 JSON 때문에 파이프라인이 깨진 적이 있다면, PydanticAI를 검토할 만합니다. &lt;a href="https://pydantic.dev/docs/ai/overview/" rel="noopener noreferrer"&gt;Pydantic&lt;/a&gt; 팀이 만든 Python 에이전트 프레임워크로, 에이전트 출력과 도구 호출을 Pydantic 모델로 검증하는 데 초점을 둡니다. 이 글에서는 PydanticAI의 핵심 개념, 기본 구현 방식, 그리고 &lt;a href="https://apidog.com/kr/blog/langgraph?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;LangGraph&lt;/a&gt; 같은 Python 에이전트 프레임워크와의 차이를 정리합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;오늘 Apidog를 사용해 보세요&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  PydanticAI란 무엇인가
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pydantic.dev/docs/ai/overview/" rel="noopener noreferrer"&gt;PydanticAI&lt;/a&gt;는 Python용 오픈 소스, 공급업체 독립적인 에이전트 프레임워크입니다. Pydantic Validation 및 Pydantic Logfire를 개발하는 팀이 유지 관리하며, 목표는 에이전트 개발에 “FastAPI 같은” 타입 안전성과 개발 경험을 제공하는 것입니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpydantic.dev%2Fdocs%2Fai%2Fimg%2Fpydantic-ai-dark.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpydantic.dev%2Fdocs%2Fai%2Fimg%2Fpydantic-ai-dark.svg" alt="Pydantic AI" width="741" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PydanticAI에서는 다음 세 가지를 명확히 정의합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;에이전트가 따라야 할 지침&lt;/li&gt;
&lt;li&gt;에이전트가 호출할 수 있는 도구&lt;/li&gt;
&lt;li&gt;에이전트가 반환해야 하는 출력 구조&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;프레임워크는 모델 호출을 실행하고, 결과를 Pydantic 모델로 검증합니다. 모델이 유효하지 않은 값을 반환하면 검증 오류를 기반으로 재시도합니다.&lt;/p&gt;

&lt;p&gt;설치는 다음 중 하나로 시작할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pydantic-ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;또는:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add pydantic-ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PydanticAI는 여러 베타 버전을 거쳐 2026년 6월 23일 안정적인 v2.0.0 릴리스에 도달했습니다. v2는 도구, 훅, 지침, 모델 설정을 재사용 가능한 단위로 구성하는 하네스 우선 설계를 지향합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  왜 타입 안전성이 에이전트에게 중요한가
&lt;/h2&gt;

&lt;p&gt;LLM은 비결정적입니다. 같은 프롬프트를 두 번 실행해도 서로 다른 형태의 응답을 받을 수 있습니다. 채팅 UI에서는 큰 문제가 아닐 수 있지만, 모델 출력을 다음과 같은 코드에 연결하면 문제가 됩니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;데이터베이스 쓰기&lt;/li&gt;
&lt;li&gt;REST API 호출&lt;/li&gt;
&lt;li&gt;결제 또는 청구 계산&lt;/li&gt;
&lt;li&gt;사용자 권한 판단&lt;/li&gt;
&lt;li&gt;자동화 워크플로 실행&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;대표적인 실패 사례는 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;코드는 &lt;code&gt;priority&lt;/code&gt;를 정수로 기대하지만, 모델은 문자열을 반환할 수 있습니다. 또는 필드를 누락하거나 JSON 앞뒤에 설명 문장을 붙일 수도 있습니다.&lt;/p&gt;

&lt;p&gt;PydanticAI는 이 문제를 출력 계약으로 해결합니다. 출력 타입을 Pydantic 모델로 정의하면, 에이전트 결과는 해당 모델과 일치해야 합니다. 유효하지 않으면 프레임워크가 LLM에 오류를 전달하고 다시 생성하도록 요청합니다.&lt;/p&gt;

&lt;p&gt;즉, 하위 코드는 문자열을 파싱하는 대신 검증된 Python 객체를 받습니다.&lt;/p&gt;

&lt;p&gt;도구 호출도 동일합니다. 모델이 도구를 호출할 때 PydanticAI는 함수의 타입 힌트를 기준으로 인수를 검증합니다. 잘못된 인수는 비즈니스 로직까지 도달하지 않습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  핵심 개념
&lt;/h2&gt;

&lt;p&gt;PydanticAI로 에이전트를 만들 때 주로 다루는 개념은 다음 다섯 가지입니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;에이전트&lt;/li&gt;
&lt;li&gt;타입이 지정된 출력&lt;/li&gt;
&lt;li&gt;도구&lt;/li&gt;
&lt;li&gt;종속성&lt;/li&gt;
&lt;li&gt;모델 공급업체 및 스트리밍&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. 에이전트 만들기
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Agent&lt;/code&gt; 클래스가 기본 진입점입니다. 모델 식별자와 지침을 전달해 에이전트를 생성합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;anthropic:claude-sonnet-4-6&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Be concise, reply with one sentence.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Where does &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; come from?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;모델을 바꾸려면 모델 문자열만 변경하면 됩니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;openai:gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Be concise, reply with one sentence.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 방식은 코드 구조를 공급업체에 강하게 묶지 않도록 해줍니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 타입이 지정된 출력 사용하기
&lt;/h2&gt;

&lt;p&gt;구조화된 응답이 필요하면 Pydantic 모델을 정의하고 &lt;code&gt;output_type&lt;/code&gt;으로 전달합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SupportTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;openai:gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SupportTicket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;My payment failed three times today.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이제 &lt;code&gt;result.output&lt;/code&gt;은 단순 문자열이 아니라 &lt;code&gt;SupportTicket&lt;/code&gt; 객체입니다.&lt;/p&gt;

&lt;p&gt;예를 들어 모델이 다음처럼 반환하면:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Payment failed three times today."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;priority&lt;/code&gt;가 &lt;code&gt;int&lt;/code&gt;가 아니므로 검증이 실패합니다. PydanticAI는 오류를 모델에 전달하고 올바른 구조로 다시 생성하도록 요청합니다.&lt;/p&gt;

&lt;p&gt;실무에서는 출력 모델을 더 구체적으로 만들 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SupportTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;billing&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;technical&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 하면 다음 조건을 강제할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;category&lt;/code&gt;는 허용된 값 중 하나여야 함&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;priority&lt;/code&gt;는 1부터 5 사이의 정수여야 함&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;summary&lt;/code&gt;는 반드시 있어야 함&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. 도구 등록하기
&lt;/h2&gt;

&lt;p&gt;도구는 에이전트가 외부 시스템과 상호작용할 수 있게 합니다.&lt;/p&gt;

&lt;p&gt;예:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;데이터베이스 조회&lt;/li&gt;
&lt;li&gt;REST API 호출&lt;/li&gt;
&lt;li&gt;내부 서비스 호출&lt;/li&gt;
&lt;li&gt;계산 실행&lt;/li&gt;
&lt;li&gt;사용자 정보 조회&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PydanticAI에서는 &lt;code&gt;@agent.tool&lt;/code&gt; 데코레이터로 도구를 등록합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RunContext&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;openai:gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deps_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@agent.tool&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user_balance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RunContext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return the current balance for an account.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;lookup_balance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;여기서 중요한 점은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;account_id: str&lt;/code&gt; 타입 힌트가 도구 스키마에 사용됩니다.&lt;/li&gt;
&lt;li&gt;모델이 도구를 호출할 때 인수가 검증됩니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ctx.deps&lt;/code&gt;를 통해 외부 종속성을 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예를 들어 실제 구현에서는 &lt;code&gt;ctx.deps&lt;/code&gt;에 API 기본 URL, 데이터베이스 클라이언트, 인증 토큰 등을 넣을 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. 종속성 주입 사용하기
&lt;/h2&gt;

&lt;p&gt;프로덕션 에이전트는 보통 외부 리소스가 필요합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP 클라이언트&lt;/li&gt;
&lt;li&gt;데이터베이스 연결&lt;/li&gt;
&lt;li&gt;현재 사용자 정보&lt;/li&gt;
&lt;li&gt;API 키&lt;/li&gt;
&lt;li&gt;설정 객체&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PydanticAI는 &lt;code&gt;deps_type&lt;/code&gt;과 &lt;code&gt;RunContext&lt;/code&gt;로 종속성 주입을 처리합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RunContext&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppDeps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;api_base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;openai:gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deps_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AppDeps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@agent.tool&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_order_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RunContext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AppDeps&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return the status of an order.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# 실제 구현에서는 ctx.deps.api_base_url 및 ctx.deps.api_key 사용
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch_order_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_base_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AppDeps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;secret&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Check order A123.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;테스트에서는 실제 종속성을 가짜 객체로 교체할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;test_deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AppDeps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://mock.example.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test-key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 구조는 에이전트 로직과 외부 시스템 연결을 분리하는 데 유용합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 모델 공급업체와 스트리밍
&lt;/h2&gt;

&lt;p&gt;PydanticAI는 여러 모델 공급업체를 지원합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI&lt;/li&gt;
&lt;li&gt;Anthropic&lt;/li&gt;
&lt;li&gt;Gemini&lt;/li&gt;
&lt;li&gt;DeepSeek&lt;/li&gt;
&lt;li&gt;Grok&lt;/li&gt;
&lt;li&gt;Cohere&lt;/li&gt;
&lt;li&gt;Mistral&lt;/li&gt;
&lt;li&gt;Perplexity&lt;/li&gt;
&lt;li&gt;Azure AI Foundry&lt;/li&gt;
&lt;li&gt;Amazon Bedrock&lt;/li&gt;
&lt;li&gt;자체 호스팅 모델&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;공급업체 전환은 일반적으로 모델 문자열 변경으로 처리합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;openai:gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;anthropic:claude-sonnet-4-6&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;또한 PydanticAI는 데이터가 도착하는 대로 구조화된 출력을 스트리밍할 수 있습니다. 이를 통해 부분 결과를 렌더링하면서도 타입 검증 흐름을 유지할 수 있습니다.&lt;/p&gt;

&lt;p&gt;Pydantic Logfire와의 연계도 중요합니다. 실행 추적, 디버깅, 비용 추적 같은 관찰 가능성 기능을 함께 사용할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  PydanticAI와 다른 Python 에이전트 프레임워크 비교
&lt;/h2&gt;

&lt;p&gt;하나의 “최고” 프레임워크가 있는 것은 아닙니다. 각 프레임워크는 다른 문제를 최적화합니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;프레임워크&lt;/th&gt;
&lt;th&gt;핵심 강점&lt;/th&gt;
&lt;th&gt;적합한 경우&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PydanticAI&lt;/td&gt;
&lt;td&gt;타입 안전하고 유효성이 검증된 출력 및 도구 인수&lt;/td&gt;
&lt;td&gt;프로덕션 신뢰성과 타입 지정 데이터 흐름이 중요할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apidog.com/kr/blog/langgraph?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;LangGraph&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;명시적인 상태 저장 그래프 및 제어 흐름&lt;/td&gt;
&lt;td&gt;장기 실행, 분기, 다단계 워크플로가 필요할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apidog.com/kr/blog/google-adk?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Google ADK&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Google 생태계 내 다중 에이전트 오케스트레이션&lt;/td&gt;
&lt;td&gt;Gemini 및 Vertex AI 통합이 중요할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apidog.com/kr/blog/how-to-use-openai-agents-sdk?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenAI Agents SDK&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;핸드오프 기능이 있는 OpenAI 통합&lt;/td&gt;
&lt;td&gt;OpenAI 우선 스택에서 빠르게 구축할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;PydanticAI의 강점은 검증 계층입니다. 에이전트 출력이 다른 시스템으로 전달된다면, 출력이 Pydantic 모델과 일치한다는 보장이 런타임 오류를 줄여줍니다.&lt;/p&gt;

&lt;p&gt;LangGraph는 상태 머신과 복잡한 제어 흐름에 더 적합합니다. 여러 단계, 분기, 재시도, 상태 전이가 명확한 워크플로라면 LangGraph가 더 직접적인 제어 수단을 제공합니다.&lt;/p&gt;

&lt;p&gt;OpenAI Agents SDK는 OpenAI 중심 스택에서 빠르게 시작하기 좋습니다. 에이전트 핸드오프나 &lt;a href="https://apidog.com/kr/blog/mcp-servers-openai-agents?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;MCP 서버 지원&lt;/a&gt; 같은 기능이 필요할 때 자연스러운 선택입니다.&lt;/p&gt;

&lt;p&gt;혼합해서 사용할 수도 있습니다. 예를 들어 LangGraph로 전체 워크플로를 제어하고, 특정 노드에서 PydanticAI를 사용해 검증된 구조화 출력을 생성할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  PydanticAI를 사용해야 하는 경우
&lt;/h2&gt;

&lt;p&gt;다음 조건에 해당하면 PydanticAI가 잘 맞습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;에이전트 출력이 채팅 UI가 아니라 실제 코드로 들어간다.&lt;/li&gt;
&lt;li&gt;출력 형태가 정확해야 한다.&lt;/li&gt;
&lt;li&gt;Pydantic 모델로 응답 구조를 강제하고 싶다.&lt;/li&gt;
&lt;li&gt;IDE와 타입 체커가 에이전트 결과를 이해해야 한다.&lt;/li&gt;
&lt;li&gt;이미 코드베이스에서 Pydantic 또는 FastAPI를 사용하고 있다.&lt;/li&gt;
&lt;li&gt;모델 공급업체를 바꿀 가능성이 있다.&lt;/li&gt;
&lt;li&gt;Logfire 기반 관찰 가능성이 필요하다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;반대로 다음 경우에는 다른 도구가 더 적합할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;복잡한 상태 머신이 필요하다.&lt;/li&gt;
&lt;li&gt;워크플로가 여러 분기와 장기 실행 상태를 가진다.&lt;/li&gt;
&lt;li&gt;에이전트 간 오케스트레이션이 핵심 요구사항이다.&lt;/li&gt;
&lt;li&gt;그래프 기반 제어 흐름이 더 중요하다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  에이전트 뒤의 API 테스트 및 모킹
&lt;/h2&gt;

&lt;p&gt;PydanticAI 에이전트는 의존하는 API만큼만 안정적입니다. 모든 실행은 LLM 공급업체를 호출하며, 대부분의 유용한 에이전트는 내부 REST API나 서드파티 도구도 호출합니다.&lt;/p&gt;

&lt;p&gt;이 지점에서 다음 문제가 발생할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API 응답 형태가 예상과 다름&lt;/li&gt;
&lt;li&gt;필수 필드 누락&lt;/li&gt;
&lt;li&gt;인증 실패&lt;/li&gt;
&lt;li&gt;스트리밍 형식 불일치&lt;/li&gt;
&lt;li&gt;공급업체 속도 제한&lt;/li&gt;
&lt;li&gt;테스트 중 불필요한 토큰 비용 발생&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PydanticAI는 모델의 출력은 검증할 수 있지만, 여러분이 호출하는 업스트림 API가 올바른 응답을 반환하는지는 직접 보장하지 않습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffyld2yzc9x8xwd2u8f9z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffyld2yzc9x8xwd2u8f9z.png" alt="Apidog API testing" width="799" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;이때 &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;를 사용할 수 있습니다. Apidog는 에이전트 프레임워크가 아니라, 에이전트가 통신하는 API를 테스트하고 모의하는 API 플랫폼입니다.&lt;/p&gt;

&lt;p&gt;실무에서 사용할 수 있는 방식은 다음과 같습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. LLM 또는 도구 엔드포인트 모의하기
&lt;/h3&gt;

&lt;p&gt;개발 중에는 실제 공급업체 대신 결정적인 응답을 반환하는 &lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;모의 API&lt;/a&gt;를 사용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;이렇게 하면 다음을 줄일 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;반복 테스트 중 토큰 비용&lt;/li&gt;
&lt;li&gt;공급업체 rate limit 문제&lt;/li&gt;
&lt;li&gt;비결정적 응답으로 인한 테스트 불안정성&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. REST 응답 형태 단언하기
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;@agent.tool&lt;/code&gt; 함수가 REST API를 호출한다면, 먼저 API 응답 구조를 검증해야 합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/kr/blog/api-assertions?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 단언&lt;/a&gt;을 사용하면 다음을 API 계층에서 확인할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;필수 필드 존재 여부&lt;/li&gt;
&lt;li&gt;응답 타입&lt;/li&gt;
&lt;li&gt;상태 코드&lt;/li&gt;
&lt;li&gt;중첩 객체 구조&lt;/li&gt;
&lt;li&gt;에러 응답 형식&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이렇게 하면 에이전트 실행 중간이 아니라 API 테스트 단계에서 문제를 잡을 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 환경별 키와 기본 URL 관리하기
&lt;/h3&gt;

&lt;p&gt;로컬, 스테이징, CI 환경에서 서로 다른 API 키와 기본 URL을 사용해야 한다면 Apidog 환경을 분리해 관리할 수 있습니다.&lt;/p&gt;

&lt;p&gt;예:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local&lt;/li&gt;
&lt;li&gt;Staging&lt;/li&gt;
&lt;li&gt;Production&lt;/li&gt;
&lt;li&gt;CI Mock&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;코드를 바꾸지 않고 환경만 전환하면 테스트 대상도 바꿀 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. LLM 엔드포인트 직접 검증하기
&lt;/h3&gt;

&lt;p&gt;HTTP로 LLM 공급업체를 직접 호출한다면, 에이전트에 연결하기 전에 &lt;a href="https://apidog.com/kr/blog/how-to-test-chatgpt-api-with-apidog?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;ChatGPT API 테스트&lt;/a&gt;를 통해 다음을 확인할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;인증 헤더&lt;/li&gt;
&lt;li&gt;요청 본문&lt;/li&gt;
&lt;li&gt;스트리밍 응답&lt;/li&gt;
&lt;li&gt;도구 호출 형식&lt;/li&gt;
&lt;li&gt;에러 응답&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apidog는 PydanticAI의 대안이 아닙니다. PydanticAI가 에이전트와 타입 검증을 담당한다면, Apidog는 에이전트가 호출하는 API 표면을 테스트하고 모의하는 역할을 합니다.&lt;/p&gt;

&lt;p&gt;시작하려면 &lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 다운로드&lt;/a&gt;하고, 먼저 에이전트 도구 엔드포인트 하나를 모의해보세요.&lt;/p&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문
&lt;/h2&gt;

&lt;h3&gt;
  
  
  PydanticAI는 무료이며 오픈 소스인가요?
&lt;/h3&gt;

&lt;p&gt;네. PydanticAI는 오픈 소스이며 PyPI에서 설치할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pydantic-ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;또는:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add pydantic-ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;다만 사용하는 LLM 공급업체의 API 비용은 별도로 발생합니다. 개발 중 비용을 줄이려면 매번 실제 모델을 호출하는 대신 &lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;테스트 중 API 응답을 모의&lt;/a&gt;할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  PydanticAI는 어떤 모델과 작동하나요?
&lt;/h3&gt;

&lt;p&gt;공급업체 독립적으로 설계되어 있습니다. 문서에는 OpenAI, Anthropic, Gemini, DeepSeek, Grok, Cohere, Mistral, Perplexity가 포함되어 있으며, Azure AI Foundry, Amazon Bedrock, 자체 호스팅 모델도 지원 대상으로 나열되어 있습니다.&lt;/p&gt;

&lt;p&gt;모델은 &lt;code&gt;Agent&lt;/code&gt; 생성자에 문자열로 지정합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;anthropic:claude-sonnet-4-6&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;또는:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;openai:gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;전환은 일반적으로 이 문자열을 바꾸는 방식으로 처리합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  PydanticAI는 LangChain 또는 LangGraph와 어떻게 다른가요?
&lt;/h3&gt;

&lt;p&gt;PydanticAI는 타입 안전성과 검증된 구조화 출력에 집중합니다. Pydantic 모델을 기반으로 출력과 도구 인수를 검증합니다.&lt;/p&gt;

&lt;p&gt;LangGraph는 다단계, 분기, 장기 실행 워크플로를 위한 명시적인 상태 저장 그래프에 집중합니다.&lt;/p&gt;

&lt;p&gt;정리하면 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;출력 형태 보장과 타입 지정 데이터 흐름이 중요하면 PydanticAI&lt;/li&gt;
&lt;li&gt;복잡한 상태 머신과 그래프 기반 제어가 중요하면 LangGraph&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  사용하려면 Pydantic을 알아야 하나요?
&lt;/h3&gt;

&lt;p&gt;기본 개념을 알면 좋습니다. PydanticAI에서는 &lt;code&gt;BaseModel&lt;/code&gt;을 상속한 클래스로 데이터 형태를 정의합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FastAPI를 사용해본 적이 있거나 &lt;a href="https://apidog.com/kr/blog/python-api-testing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 테스트용 Python&lt;/a&gt;을 다뤄본 적이 있다면 익숙한 방식입니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  결론
&lt;/h2&gt;

&lt;p&gt;PydanticAI는 에이전트 개발에서 자주 발생하는 문제를 직접 다룹니다. 모델 출력과 도구 호출이 선언한 타입과 일치하는지 검증하고, 유효하지 않으면 재시도합니다.&lt;/p&gt;

&lt;p&gt;다음이 중요하다면 PydanticAI를 선택하세요.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;프로덕션 신뢰성&lt;/li&gt;
&lt;li&gt;구조화된 출력&lt;/li&gt;
&lt;li&gt;Pydantic 기반 검증&lt;/li&gt;
&lt;li&gt;타입 체커와 IDE 지원&lt;/li&gt;
&lt;li&gt;공급업체 유연성&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;반대로 복잡한 그래프 오케스트레이션이 핵심이라면 LangGraph 같은 프레임워크가 더 적합할 수 있습니다.&lt;/p&gt;

&lt;p&gt;어떤 에이전트 프레임워크를 사용하든, 에이전트 아래의 API는 별도로 테스트해야 합니다. LLM 및 도구 엔드포인트를 모의하고, 응답 형태를 단언하며, &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;에서 환경별 키를 관리하면 에이전트를 더 검증된 기반 위에서 실행할 수 있습니다.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>APIDOG 6월 업데이트: AI 기반 CLI 워크플로우, 가져오기 개선, OAuth 2.0 자동 새로 고침</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Fri, 26 Jun 2026 06:18:42 +0000</pubDate>
      <link>https://dev.to/rihpig/apidog-6weol-eobdeiteu-ai-giban-cli-weokeupeulrou-gajyeoogi-gaeseon-oauth-20-jadong-saero-gocim-3f05</link>
      <guid>https://dev.to/rihpig/apidog-6weol-eobdeiteu-ai-giban-cli-weokeupeulrou-gajyeoogi-gaeseon-oauth-20-jadong-saero-gocim-3f05</guid>
      <description>&lt;p&gt;6월 업데이트에서는 AI 기반 CLI 작업, 더 깔끔한 가져오기, OAuth 2.0 자동 토큰 갱신, 테스트/구성 워크플로우 개선을 통해 일상적인 API 작업을 더 쉽게 자동화하고 안정적으로 실행할 수 있도록 했습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;오늘 Apidog 사용해 보기&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;이번 릴리스의 핵심은 API 팀이 반복적으로 시간을 쓰는 지점을 줄이는 것입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI 에이전트가 실제 Apidog 프로젝트 리소스를 안전하게 다루도록 지원&lt;/li&gt;
&lt;li&gt;Postman/OpenAPI/Swagger 가져오기 후 수동 정리 감소&lt;/li&gt;
&lt;li&gt;OAuth 2.0 보호 API 요청 중 토큰 만료로 인한 중단 최소화&lt;/li&gt;
&lt;li&gt;테스트 스위트, 예약 작업, 웹 앱 요청 헤더 구성 개선&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  새로운 업데이트
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Apidog CLI: AI 기반 API 워크플로우 실행 레이어
&lt;/h2&gt;

&lt;p&gt;Apidog CLI는 AI 기반 API 워크플로우의 실행 레이어 역할을 강화하고 있습니다.&lt;/p&gt;

&lt;p&gt;목표는 사용자가 모든 CLI 명령을 직접 익히는 것이 아니라, AI 에이전트가 Apidog 프로젝트 리소스를 구조화된 방식으로 읽고, 변경하고, 검증하고, 내보내고, 테스트할 수 있도록 하는 것입니다.&lt;/p&gt;

&lt;p&gt;이번 업데이트에서 Apidog CLI는 다음 작업을 지원합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apidog 프로젝트 리소스에서 동작하는 AI 에이전트 실행 레이어로 활용&lt;/li&gt;
&lt;li&gt;테스트 케이스 실행&lt;/li&gt;
&lt;li&gt;시나리오 케이스에서 엔드포인트, 테스트 케이스, 다른 시나리오 참조&lt;/li&gt;
&lt;li&gt;네이티브 형식 및 OpenAPI 형식 내보내기 시 더 세분화된 범위 제어&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예를 들어 AI 에이전트가 API 테스트를 생성하거나 기존 시나리오를 수정하는 흐름에서는 다음과 같은 패턴을 만들 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. AI 에이전트가 Apidog 프로젝트 리소스를 읽음
2. 필요한 엔드포인트 또는 테스트 케이스를 참조
3. 변경 사항 또는 테스트 시나리오를 생성
4. CLI로 실행 또는 검증
5. 필요한 범위만 내보내기
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apidog Skills와 함께 사용하면 CLI는 AI 에이전트에 더 명확한 작업 지침과 안전한 실행 경계를 제공합니다. 에이전트는 Apidog 리소스 사용 방식을 더 잘 이해하고, 생성한 변경 사항을 다시 작성하기 전에 검증하며, 추측을 줄인 상태로 API 작업을 완료할 수 있습니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CLI는 자연어 AI 지침과 구조화된 Apidog 프로젝트 작업 사이의 브리지 역할을 합니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  가져오기 및 내보내기 개선
&lt;/h2&gt;

&lt;p&gt;이번 릴리스는 Postman에서 데이터를 이전하거나 OpenAPI/Swagger 기반으로 API 정의를 관리하는 팀에 유용한 가져오기/내보내기 개선을 포함합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Postman API 가져오기 후 정리 감소
&lt;/h3&gt;

&lt;p&gt;Postman API를 통해 데이터를 가져올 때 Apidog는 이제 변수 이름의 공백을 제거할 수 있습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 다음과 같은 변수 이름은 가져오기 후 사용하기 불편할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base url
access token
user id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;공백 제거를 적용하면 다운스트림 요청, 스크립트, 환경 변수 참조에서 더 일관된 이름을 사용할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;baseurl
accesstoken
userid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;또한 Postman API를 통해 워크스페이스를 가져올 때, Apidog는 워크스페이스 생성자를 기반으로 혼란스러운 &lt;code&gt;"내 워크스페이스"&lt;/code&gt; 이름을 변경할 수 있습니다. 여러 워크스페이스를 가져오는 팀은 가져온 데이터의 출처를 더 쉽게 식별하고 정리할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenAPI 및 Swagger 가져오기/내보내기 개선
&lt;/h3&gt;

&lt;p&gt;OpenAPI 및 Swagger 가져오기/내보내기는 객체 유형 매개변수와 참조 유형 매개변수를 지원합니다.&lt;/p&gt;

&lt;p&gt;즉, 더 복잡한 API 사양 구조를 가져오거나 내보낼 때 수동으로 보정해야 하는 경우가 줄어듭니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;
&lt;th&gt;이전&lt;/th&gt;
&lt;th&gt;현재&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;ul&gt;
&lt;li&gt;가져온 변수에 수동 정리가 필요할 수 있었습니다.&lt;/li&gt;
&lt;li&gt;여러 가져온 워크스페이스에 혼란스러운 이름이 있을 수 있었습니다.&lt;/li&gt;
&lt;li&gt;복잡한 OpenAPI 매개변수는 가져오기/내보내기 후 추가 조정이 필요할 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;&lt;/td&gt;
      &lt;td&gt;&lt;ul&gt;
&lt;li&gt;Postman API 가져오기는 변수 이름에서 공백을 제거할 수 있습니다.&lt;/li&gt;
&lt;li&gt;모호한 워크스페이스 이름은 더 쉬운 식별을 위해 변경될 수 있습니다.&lt;/li&gt;
&lt;li&gt;OpenAPI/Swagger 가져오기/내보내기는 객체 유형 및 참조 유형 매개변수를 지원합니다.&lt;/li&gt;
&lt;/ul&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  OAuth 2.0: 자동 토큰 갱신 지원
&lt;/h2&gt;

&lt;p&gt;OAuth 2.0 인증은 이제 자동 토큰 갱신을 지원합니다.&lt;/p&gt;

&lt;p&gt;액세스 토큰이 만료되었거나 만료가 임박한 경우, Apidog는 자동으로 토큰을 갱신할 수 있습니다. 따라서 사용자는 요청 중간에 다시 인증하거나 새 토큰을 복사해 붙여 넣을 필요가 줄어듭니다.&lt;/p&gt;

&lt;p&gt;OAuth로 보호된 API를 반복적으로 디버깅하는 경우 특히 유용합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;기존 흐름:
요청 실행 → 토큰 만료 → 요청 실패 → 재인증 → 토큰 복사 → 다시 요청

개선된 흐름:
요청 실행 → 토큰 만료 감지 → 자동 갱신 → 요청 계속
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;API 디버깅, 테스트, 반복적인 요청 검증 중 인증으로 인한 중단을 줄일 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  사용자 피드백 기반 개선 사항
&lt;/h2&gt;

&lt;h2&gt;
  
  
  MCP 클라이언트 호환성 개선
&lt;/h2&gt;

&lt;p&gt;Apidog는 MCP 클라이언트 호환성을 개선했으며, 이제 비표준 스키마를 더 안정적으로 구문 분석할 수 있습니다.&lt;/p&gt;

&lt;p&gt;이는 MCP 서버 또는 도구의 스키마 출력이 예상 형식을 엄격히 따르지 않는 경우에 유용합니다. 스키마 차이로 초기에 실패하는 대신, Apidog는 더 많은 실제 MCP 응답을 처리하고 도구 통합 및 디버깅 성공률을 높일 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  테스트 스위트에서 이름으로 정적 단계 검색
&lt;/h2&gt;

&lt;p&gt;이제 테스트 스위트에 정적 단계를 추가할 때 이름으로 검색할 수 있습니다.&lt;/p&gt;

&lt;p&gt;엔드포인트, 테스트 케이스, 시나리오가 많은 프로젝트에서는 긴 목록을 직접 스크롤하는 대신 이름으로 필요한 단계를 빠르게 찾을 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;예시 워크플로우:
1. 테스트 스위트 열기
2. 정적 단계 추가 선택
3. 단계 이름 검색
4. 필요한 엔드포인트/테스트 케이스/시나리오 추가
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  예약된 작업에 "8시간마다" 옵션 추가
&lt;/h2&gt;

&lt;p&gt;예약된 작업에 &lt;code&gt;"8시간마다"&lt;/code&gt; 실행 옵션이 추가되었습니다.&lt;/p&gt;

&lt;p&gt;다음과 같은 워크플로우에 사용할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;반복적인 자동 테스트&lt;/li&gt;
&lt;li&gt;모니터링 성격의 API 검사&lt;/li&gt;
&lt;li&gt;주기적인 API 유효성 검증&lt;/li&gt;
&lt;li&gt;하루 3회 실행이 필요한 회귀 테스트&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Apidog 웹 앱: 자동 생성 헤더 구성 지원
&lt;/h2&gt;

&lt;p&gt;Apidog 웹 앱은 이제 자동 생성 헤더 구성을 지원합니다.&lt;/p&gt;

&lt;p&gt;웹 앱 사용자는 생성된 요청 헤더를 팀 또는 프로젝트 요구 사항에 맞게 조정할 수 있습니다. 브라우저 기반 워크플로우에서도 요청 동작을 더 세밀하게 제어할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  버그 수정 및 사소한 개선 사항
&lt;/h2&gt;

&lt;p&gt;이번 달에는 다음 수정 사항과 개선 사항도 포함되었습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;자식 브랜치에서 대규모 테스트 시나리오를 선택할 때 성능이 향상되어 타임아웃 오류 가능성이 줄어듭니다.&lt;/li&gt;
&lt;li&gt;스프린트 브랜치 및 일반 브랜치 목록에서 브랜치 ID 표시 및 복사를 지원합니다.&lt;/li&gt;
&lt;li&gt;macOS가 인트라넷 요청을 보낼 수 없을 때 더 사용자 친화적인 메시지가 표시됩니다.&lt;/li&gt;
&lt;li&gt;새 모듈로 Apidog 데이터를 다시 가져올 때 서비스 기본 URL이 가져와지지 않고 엔드포인트가 지정된 서비스에 바인딩되지 않던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;요청 헤더에 추가되도록 설정된 OAuth 1.0 인증이 실제로 요청 헤더에 추가되지 않던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;Basic Auth가 중국어 변수를 사용할 때 생성된 엔드포인트 요청 코드가 올바르게 작동하지 않던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;엔드포인트가 HTTP를 사용할 때 생성된 요청 코드가 잘못 HTTPS를 사용하던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;시나리오 단계가 원시 형식 응답 본문을 참조할 때 CLI 시나리오 실행 시 &lt;code&gt;"예상치 못한 토큰"&lt;/code&gt; 오류가 보고될 수 있던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;자동화된 테스트 시나리오가 비정상적으로 종료된 후에도 테스트 보고서 세부 정보가 계속 실행 중으로 표시되던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;루트 폴더에서 시나리오 케이스를 실행한 후 해당 보고서가 테스트 보고서 목록에 표시되지 않던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;Apidog 웹 앱을 새로 고칠 때 프로젝트가 자동으로 메인 브랜치로 다시 전환되던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;브랜치 가져오기 및 Markdown에 엔드포인트를 삽입할 때 태그 필터 옵션에 내용이 없던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;여러 모듈과 서비스가 있는 Apidog 파일을 가져올 때 비기본 서비스가 반복적으로 추가되던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;Apidog 데이터를 가져올 때 Markdown 문서 태그가 올바르게 가져와지지 않던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;일부 경우에 엔드포인트를 메인 브랜치로 병합한 후에도 충돌이 계속 표시되던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;일부 경우에 SSE 엔드포인트를 디버깅할 때 프론트엔드 오류가 발생할 수 있던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;On-Premises 프로젝트 통계가 기본 모듈의 데이터만 계산하던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;팀 세부 정보 페이지에서 다중 모듈 Apidog 파일을 가져올 때 엔드포인트가 기본 모듈로 잘못 가져와지던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;수정 사항을 제출한 후 일부 경우에 변경 사항이 사라지던 문제를 수정했습니다.&lt;/li&gt;
&lt;li&gt;비밀번호 변경 시 이메일 인증 흐름에서 잘못된 오류 메시지가 표시되던 문제를 수정했습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  개발팀에 의미하는 바
&lt;/h2&gt;

&lt;p&gt;이번 업데이트는 사람, AI 에이전트, API 사양, 자동화된 테스트가 함께 동작하는 워크플로우를 더 안정적으로 만듭니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;
&lt;th&gt;영역&lt;/th&gt;
&lt;th&gt;개선 사항&lt;/th&gt;
&lt;th&gt;중요성&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
&lt;td&gt;AI 기반 CLI 워크플로우&lt;/td&gt;
&lt;td&gt;CLI는 AI 에이전트가 실제 Apidog 프로젝트 리소스에서 작업하고, 테스트 케이스를 실행하고, 기존 자산을 참조하고, 내보내기를 더 정확하게 제어하는 데 도움이 될 수 있습니다.&lt;/td&gt;
&lt;td&gt;AI 에이전트가 구조화되지 않은 정보에서 추측하는 대신 구조화된 프로젝트 컨텍스트를 사용하여 API 작업을 더 쉽게 완료할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;가져오기 및 내보내기&lt;/td&gt;
&lt;td&gt;Postman API 가져오기는 변수 이름을 정리하고 워크스페이스 이름을 명확히 할 수 있습니다; OpenAPI/Swagger 가져오기/내보내기는 객체 유형 및 참조 유형 매개변수를 지원합니다.&lt;/td&gt;
&lt;td&gt;마이그레이션 후 정리 작업이 줄어들고 API 사양 교환이 더 완벽해집니다.&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;인증&lt;/td&gt;
&lt;td&gt;OAuth 2.0 토큰이 자동으로 갱신될 수 있습니다.&lt;/td&gt;
&lt;td&gt;API 디버깅 및 테스트 중 중단이 줄어듭니다.&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;MCP 호환성&lt;/td&gt;
&lt;td&gt;MCP 클라이언트가 더 많은 비표준 스키마를 구문 분석할 수 있습니다.&lt;/td&gt;
&lt;td&gt;실제 MCP 도구 및 서버와의 호환성이 향상됩니다.&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;테스트 워크플로우&lt;/td&gt;
&lt;td&gt;정적 단계를 더 쉽게 찾을 수 있으며, 예약된 작업은 추가적인 8시간 간격을 지원합니다.&lt;/td&gt;
&lt;td&gt;더 빠른 테스트 스위트 설정과 더 유연한 반복 테스트 실행.&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;웹 앱 구성&lt;/td&gt;
&lt;td&gt;Apidog 웹 앱에서 자동 생성 헤더를 구성할 수 있습니다.&lt;/td&gt;
&lt;td&gt;브라우저 기반 워크플로우에서 요청 동작에 대한 더 많은 제어 권한.&lt;/td&gt;
&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;정리하면 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI 에이전트 → 더 나은 프로젝트 컨텍스트 확보
가져온 데이터 → 수동 정리 감소
OAuth 요청 → 토큰 만료 중단 감소
테스트 워크플로우 → 검색 및 예약 옵션 개선
웹 앱 요청 → 헤더 구성 제어 강화
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  대화에 참여하세요
&lt;/h2&gt;

&lt;p&gt;동료 API 엔지니어 및 Apidog 팀과 연결하세요.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;실시간 토론 및 지원을 위해 &lt;a href="https://discord.com/invite/ZBxrzyXfbJ?ref=apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Discord&lt;/a&gt; 커뮤니티에 참여하세요.&lt;/li&gt;
&lt;li&gt;기술적인 대화를 위해 &lt;a href="https://join.slack.com/t/apidogcommunity/shared_invite/zt-2neie4nh2-4_zhufuNBmCq4EtI6fZUwA?ref=apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Slack&lt;/a&gt; 커뮤니티에 참여하세요.&lt;/li&gt;
&lt;li&gt;최신 업데이트를 위해 &lt;a href="https://x.com/ApidogHQ?ref=apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;X (Twitter)&lt;/a&gt;를 팔로우하세요.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;추신. 모든 업데이트에 대한 자세한 내용은 &lt;a href="https://apidog.canny.io/changelog/?ref=apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog Changelog&lt;/a&gt;를 확인하세요.&lt;/p&gt;

&lt;p&gt;감사합니다,&lt;br&gt;&lt;br&gt;
Apidog 팀 드림&lt;/p&gt;

</description>
    </item>
    <item>
      <title>GPT-5.6에 무슨 일이?</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Fri, 26 Jun 2026 05:24:38 +0000</pubDate>
      <link>https://dev.to/rihpig/gpt-56e-museun-ili-1lj3</link>
      <guid>https://dev.to/rihpig/gpt-56e-museun-ili-1lj3</guid>
      <description>&lt;p&gt;OpenAI의 다음 주력 모델로 알려진 GPT-5.6은 일반적인 방식으로 바로 공개 출시되지 않을 가능성이 큽니다. 2026년 6월 25일 보도에 따르면, 미국 정부는 OpenAI에 공개 출시를 보류하고 소수의 검증된 파트너에게 먼저 모델을 제공할 것을 요청했습니다. 이는 2주 전 Anthropic이 정부 지시에 따라 &lt;a href="https://apidog.com/kr/blog/claude-fable-5-rate-limits?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Fable 5 및 Mythos 5 모델&lt;/a&gt;을 오프라인으로 전환해야 했던 사례와 같은 흐름입니다. 결론은 단순합니다. 첨단 모델 API를 기반으로 제품을 만든다면, 특정 모델 하나에 강하게 결합된 아키텍처는 더 이상 안전하지 않습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;오늘 Apidog 사용해 보기&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  GPT-5.6에 무슨 일이 일어나고 있는가
&lt;/h2&gt;

&lt;p&gt;현재까지 알려진 내용은 공식 발표가 아니라 보도 기반입니다. OpenAI와 백악관 모두 세부 사항을 공개적으로 확정하지 않았기 때문에, 아래 내용은 “보고된 상황”으로 봐야 합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;요청 주체:&lt;/strong&gt; 트럼프 행정부, 특히 국가 사이버 국장실과 과학기술정책실이 OpenAI에 단계별 출시를 요청한 것으로 보도되었습니다. 이 내용은 &lt;a href="https://www.theinformation.com/articles/trump-administration-asks-openai-stagger-release-new-model-security-concerns" rel="noopener noreferrer"&gt;The Information&lt;/a&gt;, &lt;a href="https://www.axios.com/2026/06/25/trump-administration-openai-gpt-model-release" rel="noopener noreferrer"&gt;Axios&lt;/a&gt;, &lt;a href="https://siliconangle.com/2026/06/25/openai-staggers-gpt-5-6-rollout-government-vetting-eyes-2027-ipo/" rel="noopener noreferrer"&gt;SiliconANGLE&lt;/a&gt;에서 다뤄졌습니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;단계별 출시의 의미:&lt;/strong&gt; 공개 API에 즉시 개방하는 대신, GPT-5.6은 먼저 소수의 파트너에게 제공될 수 있습니다. 보도에 따르면 미리보기 기간에는 고객별 정부 승인이 필요할 수 있습니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;이유:&lt;/strong&gt; 국가 안보입니다. 특히 모델이 소프트웨어 취약점 탐지, 공격 경로 구성, 강화된 시스템 침투 등에 활용될 수 있다는 우려가 언급되었습니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;시기:&lt;/strong&gt; 6월 출시 기대는 지연되었습니다. 6월 말 출시를 예상했던 예측 시장은 약화되었고, 2026년 7월 출시 가능성이 더 높게 거론되고 있습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;즉, GPT-5.6은 제품 출시라기보다 통제된 배포에 가까운 방식으로 다뤄지고 있습니다. 현재 공개 API에서 사용 가능한 주력 모델은 여전히 GPT-5.5로 알려져 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fable 5와 Mythos 5 사례가 이미 있었다
&lt;/h2&gt;

&lt;p&gt;GPT-5.6 상황은 독립적인 사건이 아닙니다. 2026년 6월 12일, Anthropic은 정부 지시에 따라 새로 발표한 Fable 5 및 Mythos 5 모델을 비활성화해야 했습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.cnbc.com/2026/06/12/anthropic-disables-access-to-fable-5-and-mythos-5-to-comply-with-government-directive.html" rel="noopener noreferrer"&gt;CNBC&lt;/a&gt;, &lt;a href="https://fortune.com/2026/06/13/anthropic-disables-fable-mythos-export-controls-national-security-threat/" rel="noopener noreferrer"&gt;Fortune&lt;/a&gt;, &lt;a href="https://www.anthropic.com/news/fable-mythos-access" rel="noopener noreferrer"&gt;Anthropic 공식 성명&lt;/a&gt;에 따르면 핵심은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;이 조치는 국가 안보 당국을 인용한 수출 통제 지시였습니다.&lt;/li&gt;
&lt;li&gt;Anthropic은 모든 외국인에 대한 모델 접근을 중단하라는 지시를 받았습니다.&lt;/li&gt;
&lt;li&gt;촉발 요인은 Fable 5의 안전장치를 우회하는 기술이었습니다. 해당 안전장치는 Mythos 5의 더 강력한 사이버 보안 기능 접근을 차단하도록 설계된 것으로 설명되었습니다.&lt;/li&gt;
&lt;li&gt;Anthropic은 외국인과 미국인을 실시간으로 확실하게 구분할 수 없었기 때문에, 결과적으로 모든 사용자에게 모델을 비활성화했습니다.&lt;/li&gt;
&lt;li&gt;Anthropic은 명령을 따랐지만, 좁은 범위의 단일 탈옥 사례가 광범위하게 배포된 모델 회수로 이어져야 하는지에 대해서는 반발했습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GPT-5.6 보도에서 이 사례가 자주 언급되는 이유는 명확합니다. 첨단 모델이 사이버 기능을 갖출수록, 출시 여부와 접근 범위가 공급업체만의 결정이 아니게 됩니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  개발자에게 중요한 변화
&lt;/h2&gt;

&lt;p&gt;이 이슈는 정책 뉴스가 아니라 운영 리스크입니다.&lt;/p&gt;

&lt;p&gt;예를 들어 제품의 핵심 기능이 특정 모델 하나에 의존한다고 가정해 보겠습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;사용자 요청
  -&amp;gt; 백엔드
  -&amp;gt; 특정 모델 API
  -&amp;gt; 결과 후처리
  -&amp;gt; 사용자 응답
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 구조에서 모델이 갑자기 비활성화되면 재시도 로직은 도움이 되지 않습니다. 장애 원인이 네트워크, rate limit, 일시적 5xx가 아니라 접근 권한 자체의 철회일 수 있기 때문입니다.&lt;/p&gt;

&lt;p&gt;GPT-5.6처럼 단계별 출시되는 모델도 비슷합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;출시일에 맞춰 기능을 배포하려 했지만 접근 권한이 없을 수 있습니다.&lt;/li&gt;
&lt;li&gt;벤치마크를 미리 실행할 수 없을 수 있습니다.&lt;/li&gt;
&lt;li&gt;승인된 파트너와 일반 API 사용자의 사용 가능 시점이 다를 수 있습니다.&lt;/li&gt;
&lt;li&gt;문서상 모델은 존재하지만, 실제 프로덕션에서 호출할 수 없을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;따라서 교훈은 “첨단 모델을 쓰지 말라”가 아닙니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;교훈은 특정 모델을 교체 불가능한 종속성으로 만들지 말라는 것입니다.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  모델 장애에 대비한 구현 패턴
&lt;/h2&gt;

&lt;p&gt;정부 지시나 공급업체 정책은 통제할 수 없습니다. 대신 애플리케이션이 모델에 얼마나 강하게 결합되어 있는지는 통제할 수 있습니다.&lt;/p&gt;

&lt;p&gt;아래 패턴은 실제 서비스에서 적용하기 쉽습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. 모델 호출을 내부 인터페이스 뒤에 숨기기
&lt;/h2&gt;

&lt;p&gt;애플리케이션 코드 곳곳에서 직접 OpenAI, Anthropic, Google API를 호출하지 마세요. 대신 내부 &lt;code&gt;LLMClient&lt;/code&gt; 같은 추상 계층을 둡니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ChatMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;LLMResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;LLMClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LLMResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;그다음 공급업체별 구현을 분리합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OpenAIClient&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;LLMClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LLMResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.openai.com/v1/chat/completions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`OpenAI error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_MODEL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 하면 모델 변경은 코드 수정이 아니라 구성 변경에 가까워집니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM_PROVIDER=openai
OPENAI_MODEL=gpt-5.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;나중에 다른 공급업체나 라우터로 전환할 때도 호출부는 그대로 유지할 수 있습니다.&lt;/p&gt;

&lt;p&gt;공급업체 독립적인 설계를 고민한다면 &lt;a href="https://apidog.com/kr/blog/best-openrouter-alternatives?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenRouter 대안&lt;/a&gt;과 &lt;a href="https://apidog.com/kr/blog/how-to-use-litellm?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;LiteLLM 사용 가이드&lt;/a&gt;를 참고할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 장애 조치용 모델을 미리 정의하기
&lt;/h2&gt;

&lt;p&gt;모델이 비활성화된 뒤에 대체 모델을 찾으면 늦습니다. 프로덕션 배포 전부터 fallback 순서를 정해두는 것이 좋습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-5.5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anthropic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fallback-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fallback-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;callWithFallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`All model providers failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;실제 운영에서는 모든 오류에 대해 즉시 fallback하면 안 됩니다. 예를 들어 400 계열의 잘못된 요청은 fallback해도 실패할 가능성이 큽니다.&lt;/p&gt;

&lt;p&gt;권장 분류는 다음과 같습니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;오류 유형&lt;/th&gt;
&lt;th&gt;fallback 권장 여부&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;401 / 403 접근 거부&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;404 모델 없음&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;429 rate limit&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5xx 공급업체 장애&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400 잘못된 요청 형식&lt;/td&gt;
&lt;td&gt;아니요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;응답 파싱 실패&lt;/td&gt;
&lt;td&gt;상황에 따라 다름&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  3. 동일한 테스트 스위트를 여러 모델에 실행하기
&lt;/h2&gt;

&lt;p&gt;대체 모델이 있다고 해서 바로 프로덕션에 넣을 수 있는 것은 아닙니다. 응답 형식, 길이, JSON 안정성, 금지 케이스 처리 등이 기존 모델과 다를 수 있습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 앱이 항상 JSON 응답을 기대한다면, 테스트는 단순히 HTTP 200만 확인하면 안 됩니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"riskLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"low | medium | high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;테스트에서는 다음을 검증해야 합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;응답이 유효한 JSON인지&lt;/li&gt;
&lt;li&gt;필수 필드가 있는지&lt;/li&gt;
&lt;li&gt;enum 값이 허용 범위 안에 있는지&lt;/li&gt;
&lt;li&gt;빈 응답이 아닌지&lt;/li&gt;
&lt;li&gt;토큰 초과나 잘림이 없는지&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예시 어설션은 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateModelOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Output must be an object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summary must be a string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;riskLevel&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;riskLevel is invalid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;actions must be an array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 테스트를 기본 모델과 fallback 모델에 모두 실행해야 합니다.&lt;/p&gt;

&lt;p&gt;API 요청과 검증을 재사용하는 방법은 &lt;a href="https://apidog.com/kr/blog/how-to-test-chatgpt-api-with-apidog?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog로 ChatGPT API를 테스트하는 방법&lt;/a&gt;과 &lt;a href="https://apidog.com/kr/blog/api-assertions?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 어설션&lt;/a&gt;을 참고하세요.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. 모델 API를 목업해서 개발 중단을 막기
&lt;/h2&gt;

&lt;p&gt;모델 접근이 막히면 프로덕션만 문제가 되는 것이 아닙니다. 프런트엔드 개발, QA, CI 테스트도 멈출 수 있습니다.&lt;/p&gt;

&lt;p&gt;이를 막으려면 모델 API의 대표 응답을 목업해두세요.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mock-chatcmpl-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chat.completion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"choices"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;summary&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;예시 요약&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;riskLevel&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;low&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;actions&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;로그 확인&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;재시도&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"finish_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stop"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;개발 환경에서는 실제 모델 대신 목업 서버를 바라보게 구성합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM_BASE_URL=https://mock-api.example.com
LLM_PROVIDER=mock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 방식의 장점은 명확합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;실제 모델 장애와 무관하게 UI 개발 가능&lt;/li&gt;
&lt;li&gt;CI에서 안정적인 테스트 가능&lt;/li&gt;
&lt;li&gt;응답 포맷 변경에 대한 계약 테스트 가능&lt;/li&gt;
&lt;li&gt;rate limit 비용 없이 반복 테스트 가능&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;목업 API&lt;/a&gt;를 사용하면 실제 모델 API의 응답 구조를 흉내 내는 엔드포인트를 만들고, 프런트엔드와 백엔드 테스트를 계속 진행할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-470.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-470.png" alt="" width="799" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 모델별 비용과 지연 시간을 추적하기
&lt;/h2&gt;

&lt;p&gt;fallback은 무료가 아닙니다. 대체 모델이 더 비싸거나 느릴 수 있습니다.&lt;/p&gt;

&lt;p&gt;최소한 다음 필드는 로그로 남기는 것이 좋습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"feature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support-ticket-summary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latencyMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1830&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputTokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputTokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;320&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fallbackUsed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;장애 조치가 발생하면 다음을 바로 확인할 수 있어야 합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;어떤 기능에서 fallback이 발생했는가&lt;/li&gt;
&lt;li&gt;어떤 모델로 전환되었는가&lt;/li&gt;
&lt;li&gt;지연 시간이 얼마나 증가했는가&lt;/li&gt;
&lt;li&gt;비용이 얼마나 변했는가&lt;/li&gt;
&lt;li&gt;실패율이 줄었는가&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;기능별 비용 추적은 &lt;a href="https://apidog.com/kr/blog/track-openai-api-spend-per-feature?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;기능별 API 지출&lt;/a&gt;을 참고할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  권장 아키텍처
&lt;/h2&gt;

&lt;p&gt;모델 API를 직접 호출하는 구조보다 다음과 같은 구조가 안전합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
  -&amp;gt; Internal LLM Gateway
      -&amp;gt; Provider Adapter: OpenAI
      -&amp;gt; Provider Adapter: Anthropic
      -&amp;gt; Provider Adapter: Google
      -&amp;gt; Provider Adapter: Mock
  -&amp;gt; Logging / Cost Tracking
  -&amp;gt; Contract Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;핵심은 세 가지입니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;호출부는 공급업체를 몰라야 합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fallback 후보는 사전에 테스트되어 있어야 합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;목업 서버로 개발과 테스트를 계속할 수 있어야 합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이렇게 설계하면 특정 모델이 갑자기 사라져도 전체 앱이 멈추지 않습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GPT-5.6이 이미 출시되었나요?
&lt;/h3&gt;

&lt;p&gt;아니요. 2026년 6월 말 기준 공개 출시되지 않은 것으로 보도되었습니다. 보도에 따르면 OpenAI는 먼저 소수의 검증된 파트너에게 모델을 제공할 수 있으며, 정부 검토가 잘 진행되면 몇 주 후 더 광범위한 출시가 가능하다고 언급되었습니다. OpenAI는 공식 출시일을 확인하지 않았고, 공개 API는 여전히 GPT-5.5로 운영되는 것으로 알려져 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  정부가 GPT-5.6 출시에 개입한 이유는 무엇인가요?
&lt;/h3&gt;

&lt;p&gt;보도된 이유는 국가 안보입니다. 특히 첨단 모델이 소프트웨어 취약점을 찾거나 시스템 침투에 활용될 수 있다는 우려가 언급되었습니다. 요청은 국가 사이버 국장실과 과학기술정책실에서 나온 것으로 알려졌습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anthropic의 Fable 5와 Mythos 5에는 무슨 일이 있었나요?
&lt;/h3&gt;

&lt;p&gt;2026년 6월 12일, Anthropic은 외국인에 대한 접근을 중단하라는 수출 통제 지시를 받았습니다. 외국인 사용자와 미국인 사용자를 실시간으로 확실하게 구분할 수 없었기 때문에, Anthropic은 모든 사용자에게 Fable 5와 Mythos 5를 비활성화했습니다. 이 사례는 이후 GPT-5.6 보도에서 선례로 언급되고 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  모델이 회수되면 앱을 어떻게 계속 작동시킬 수 있나요?
&lt;/h3&gt;

&lt;p&gt;단일 모델에 직접 의존하지 않도록 설계해야 합니다. 내부 LLM 인터페이스를 만들고, 여러 공급업체 어댑터를 분리하며, fallback 모델을 사전에 테스트하세요. 또한 &lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog 목업 서버&lt;/a&gt; 같은 도구로 모델 API를 목업하면 실제 모델 접근이 막혀도 개발과 테스트를 계속할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;Fable 5와 Mythos 5가 회수된 지 2주 만에 GPT-5.6이 단계별 출시 대상으로 보도된 것은 우연으로 보기 어렵습니다. 첨단 모델은 이제 단순한 SaaS API가 아니라, 국가 안보 검토와 접근 통제의 영향을 받을 수 있는 인프라 종속성입니다.&lt;/p&gt;

&lt;p&gt;개발자가 할 일은 첨단 모델을 피하는 것이 아닙니다. 특정 모델 하나를 영구적인 전제로 삼지 않는 것입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;모델 호출을 내부 인터페이스 뒤에 숨기세요.&lt;/li&gt;
&lt;li&gt;fallback 모델을 미리 테스트하세요.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;모델 API를 목업&lt;/a&gt;하세요.&lt;/li&gt;
&lt;li&gt;모델별 비용, 지연 시간, 실패율을 추적하세요.&lt;/li&gt;
&lt;li&gt;공급업체 전환을 코드 수정이 아니라 구성 변경으로 만드세요.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이런 구조를 &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;에서 테스트, 목업, 문서화하면 단일 모델을 단일 장애 지점으로 두는 위험을 줄일 수 있습니다.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>OpenAI 함수 호출 사용법</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Fri, 26 Jun 2026 05:22:24 +0000</pubDate>
      <link>https://dev.to/rihpig/openai-hamsu-hocul-sayongbeob-117</link>
      <guid>https://dev.to/rihpig/openai-hamsu-hocul-sayongbeob-117</guid>
      <description>&lt;p&gt;이 가이드에서는 OpenAI 함수 호출(도구 호출)을 실제 애플리케이션에 연결하는 흐름을 구현합니다. 도구를 정의하고, OpenAI에 전달하고, 모델이 반환한 도구 호출을 파싱한 뒤, 구조화된 인수로 직접 함수를 실행합니다. 이후 엄격 모드와 병렬 호출을 설정하고, &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;로 도구 인수와 하위 API 계약을 검증합니다. 공식 기준은 OpenAI의 &lt;a href="https://developers.openai.com/api/docs/guides/function-calling" rel="noopener noreferrer"&gt;함수 호출 문서&lt;/a&gt;를 참고하고, 더 큰 그림은 &lt;a href="https://apidog.com/kr/blog/how-to-use-openai-agents-sdk?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenAI Agents SDK로 에이전트 구축&lt;/a&gt;을 함께 확인하세요.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;지금 Apidog를 사용해 보세요&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  시작하기 전에 필요한 것
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developers.openai.com/api/docs/guides/function-calling" rel="noopener noreferrer"&gt;함수 호출&lt;/a&gt;은 모델이 코드 또는 외부 시스템과 상호작용하는 방식입니다. 앱에서 사용할 수 있는 함수를 모델에 설명하면, 모델은 사용자 요청을 해석하고 적절한 함수 이름과 인수를 JSON 형태로 반환합니다.&lt;/p&gt;

&lt;p&gt;중요한 점은 모델이 함수를 직접 실행하지 않는다는 것입니다.&lt;/p&gt;

&lt;p&gt;흐름은 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;개발자가 함수 스키마를 정의합니다.&lt;/li&gt;
&lt;li&gt;모델이 사용자 요청에 맞는 함수 호출을 생성합니다.&lt;/li&gt;
&lt;li&gt;개발자가 &lt;code&gt;arguments&lt;/code&gt;를 파싱합니다.&lt;/li&gt;
&lt;li&gt;개발자 코드가 실제 함수를 실행합니다.&lt;/li&gt;
&lt;li&gt;실행 결과를 다시 모델에 전달합니다.&lt;/li&gt;
&lt;li&gt;모델이 최종 응답을 생성합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;예를 들어 사용자가 “파리의 날씨를 알려줘”라고 말하면, 모델은 직접 날씨 API를 호출하지 않고 다음과 같은 구조화된 호출을 반환합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Paris, France&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;따라하려면 다음이 필요합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI API 키&lt;/li&gt;
&lt;li&gt;모델이 호출할 수 있는 자체 함수&lt;/li&gt;
&lt;li&gt;함수가 기대하는 입력 스키마&lt;/li&gt;
&lt;li&gt;선택적으로 하위 API를 테스트하거나 모의 처리할 도구&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OpenAI에서는 Chat Completions API와 Responses API 모두 함수 호출을 지원합니다. 이 글에서는 두 방식의 차이도 함께 다룹니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  1단계: 도구 정의하기
&lt;/h2&gt;

&lt;p&gt;도구는 모델이 읽을 수 있는 함수 정의입니다. 일반적으로 다음 정보를 포함합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;함수 이름&lt;/li&gt;
&lt;li&gt;함수 설명&lt;/li&gt;
&lt;li&gt;인수 스키마&lt;/li&gt;
&lt;li&gt;필수 필드&lt;/li&gt;
&lt;li&gt;허용 가능한 값&lt;/li&gt;
&lt;li&gt;추가 필드 허용 여부&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;설명은 단순한 라벨이 아니라 모델에게 “언제 이 함수를 사용해야 하는지” 알려주는 지침입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chat Completions API용 도구 정의
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get the current weather for a city. Use when the user asks about temperature or conditions."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"City and country, e.g. Bogotá, Colombia"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"celsius"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fahrenheit"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Responses API용 도구 정의
&lt;/h3&gt;

&lt;p&gt;Responses API에서는 &lt;code&gt;function&lt;/code&gt; 래퍼 없이 더 평탄한 형태를 사용합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get the current weather for a city. Use when the user asks about temperature or conditions."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"City and country, e.g. Bogotá, Colombia"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"celsius"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fahrenheit"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이미 OpenAPI 사양을 관리하고 있다면 함수 인수 스키마를 거의 그대로 재사용할 수 있습니다. &lt;a href="https://apidog.com/kr/blog/api-test-collections-generation-openapi-specs?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenAPI 사양에서 테스트 컬렉션 생성&lt;/a&gt; 가이드도 함께 참고하세요.&lt;/p&gt;

&lt;h2&gt;
  
  
  2단계: 첫 번째 요청 보내기
&lt;/h2&gt;

&lt;p&gt;사용자 메시지와 도구 정의를 함께 모델에 전달합니다.&lt;/p&gt;

&lt;p&gt;Chat Completions API 예시는 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.openai.com/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "gpt-4.1",
    "messages": [
      {
        "role": "user",
        "content": "What is the weather in Paris right now?"
      }
    ],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "get_weather",
          "description": "Get the current weather for a city.",
          "parameters": {
            "type": "object",
            "properties": {
              "location": {
                "type": "string"
              }
            },
            "required": ["location"],
            "additionalProperties": false
          }
        }
      }
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tools&lt;/code&gt; 배열에는 이번 턴에서 모델에게 노출할 함수들을 넣습니다. 모델은 사용자 메시지를 읽고 다음 중 하나를 선택합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;일반 텍스트 응답&lt;/li&gt;
&lt;li&gt;하나 이상의 도구 호출&lt;/li&gt;
&lt;li&gt;도구 호출 없이 추가 설명&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;날씨 요청처럼 함수가 적합한 경우, 모델은 일반 문장이 아니라 도구 호출을 반환합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  3단계: 모델이 반환한 도구 호출 읽기
&lt;/h2&gt;

&lt;p&gt;모델이 함수를 호출하기로 결정하면 응답 구조 안에 호출 정보가 들어 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chat Completions API 응답 형태
&lt;/h3&gt;

&lt;p&gt;Chat Completions에서는 assistant 메시지의 &lt;code&gt;tool_calls&lt;/code&gt; 배열을 확인합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"call_12345xyz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Paris, France&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;여기서 확인할 필드는 다음입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt;: 나중에 함수 결과를 되돌려줄 때 사용&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;function.name&lt;/code&gt;: 실행할 함수 이름&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;function.arguments&lt;/code&gt;: JSON 문자열 형태의 인수&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Responses API 응답 형태
&lt;/h3&gt;

&lt;p&gt;Responses API에서는 &lt;code&gt;output&lt;/code&gt; 배열 안에 &lt;code&gt;function_call&lt;/code&gt; 항목이 들어갑니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function_call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"call_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"call_12345xyz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Paris, France&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;중요한 점은 &lt;code&gt;arguments&lt;/code&gt;가 객체가 아니라 JSON으로 인코딩된 문자열이라는 것입니다. 따라서 반드시 직접 파싱해야 합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolCall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;call_12345xyz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_weather&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Paris, France&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Paris, France&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;실제 코드에서는 파싱 직후 스키마 검증도 수행하는 것이 좋습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  4단계: 직접 함수 실행하기
&lt;/h2&gt;

&lt;p&gt;모델은 함수 이름과 인수만 제공합니다. 실제 실행은 애플리케이션 코드가 담당합니다.&lt;/p&gt;

&lt;p&gt;예를 들어 Node.js에서는 다음처럼 매핑할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getWeather&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;celsius&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 실제로는 외부 날씨 API 또는 내부 서비스를 호출&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cloudy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;availableFunctions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getWeather&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;executeToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;functionName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;availableFunctions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unknown function: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 단계에서 해야 할 검증은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;함수 이름이 허용된 목록에 있는지 확인&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;arguments&lt;/code&gt;가 올바른 JSON인지 확인&lt;/li&gt;
&lt;li&gt;필수 필드가 있는지 확인&lt;/li&gt;
&lt;li&gt;enum 값이 허용 범위 안에 있는지 확인&lt;/li&gt;
&lt;li&gt;비즈니스 규칙에 맞는지 확인&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예를 들어 &lt;code&gt;location&lt;/code&gt;이 문자열이어도 서비스하지 않는 지역일 수 있습니다. 스키마 검증과 비즈니스 검증은 별도로 처리해야 합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  5단계: 실행 결과를 모델에 반환하기
&lt;/h2&gt;

&lt;p&gt;함수 실행이 끝나면 결과를 모델에 다시 보내야 합니다. 그래야 모델이 사용자에게 최종 답변을 생성할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chat Completions API 방식
&lt;/h3&gt;

&lt;p&gt;Chat Completions에서는 &lt;code&gt;tool&lt;/code&gt; 역할 메시지를 추가하고, 원래 도구 호출의 &lt;code&gt;id&lt;/code&gt;를 &lt;code&gt;tool_call_id&lt;/code&gt;로 연결합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tool_call_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"call_12345xyz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Paris, France&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;temperature&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:18,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;unit&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;celsius&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;condition&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;cloudy&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;전체 흐름은 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;사용자 메시지 전송&lt;/li&gt;
&lt;li&gt;모델이 &lt;code&gt;tool_calls&lt;/code&gt; 반환&lt;/li&gt;
&lt;li&gt;앱이 함수 실행&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tool&lt;/code&gt; 메시지로 결과 전달&lt;/li&gt;
&lt;li&gt;모델이 최종 답변 생성&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Responses API 방식
&lt;/h3&gt;

&lt;p&gt;Responses API에서는 &lt;code&gt;function_call_output&lt;/code&gt; 항목을 사용하고 &lt;code&gt;call_id&lt;/code&gt;로 연결합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function_call_output"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"call_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"call_12345xyz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Paris, France&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;temperature&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:18,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;unit&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;celsius&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;condition&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;cloudy&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;핵심은 동일합니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;모델이 요청하고, 애플리케이션이 실행하고, 결과를 다시 모델에 전달합니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  6단계: 병렬 호출과 엄격 모드 설정하기
&lt;/h2&gt;

&lt;p&gt;기본 루프가 동작하면 신뢰성과 실행 방식을 조정합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  병렬 도구 호출
&lt;/h3&gt;

&lt;p&gt;기본적으로 모델은 한 번의 턴에서 여러 도구 호출을 반환할 수 있습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 사용자가 다음처럼 요청할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;서울, 파리, 뉴욕의 현재 날씨를 알려줘.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;모델은 세 개의 &lt;code&gt;get_weather&lt;/code&gt; 호출을 동시에 반환할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Seoul, South Korea&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Paris, France&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;New York, USA&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;각 호출이 독립적이라면 병렬 실행이 유리합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;toolCalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;executeToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;반대로 호출 순서가 중요하거나 이전 결과에 의존한다면 &lt;code&gt;parallel_tool_calls&lt;/code&gt;를 &lt;code&gt;false&lt;/code&gt;로 설정합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parallel_tool_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  엄격 모드
&lt;/h3&gt;

&lt;p&gt;엄격 모드는 모델이 반환하는 인수가 JSON 스키마와 일치하도록 강제합니다.&lt;/p&gt;

&lt;p&gt;OpenAI는 엄격 모드 사용을 권장합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get the current weather for a city."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"null"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"celsius"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fahrenheit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;엄격 모드에서는 다음 규칙을 지켜야 합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;모든 객체에 &lt;code&gt;additionalProperties: false&lt;/code&gt;를 설정합니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;properties&lt;/code&gt;의 모든 필드는 &lt;code&gt;required&lt;/code&gt;에 포함합니다.&lt;/li&gt;
&lt;li&gt;선택적 필드는 생략하지 않고 &lt;code&gt;null&lt;/code&gt;을 허용합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예를 들어 &lt;code&gt;unit&lt;/code&gt;이 선택 사항이라면 &lt;code&gt;required&lt;/code&gt;에서 제거하는 대신 다음처럼 정의합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"null"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"celsius"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fahrenheit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 하면 모델은 다음 중 하나를 반환합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paris, France"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"celsius"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;또는:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paris, France"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;키가 항상 존재하므로 파싱 코드가 단순해집니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  주요 설정 요약
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;설정&lt;/th&gt;
&lt;th&gt;제어하는 내용&lt;/th&gt;
&lt;th&gt;기본값&lt;/th&gt;
&lt;th&gt;변경 시점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;parallel_tool_calls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;한 턴에서 여러 도구 호출을 반환할 수 있는지 여부&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;호출 순서가 중요하거나 서로 의존할 때 &lt;code&gt;false&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;strict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;인수가 스키마와 일치해야 하는지 여부&lt;/td&gt;
&lt;td&gt;미설정 시 최선 노력&lt;/td&gt;
&lt;td&gt;프로덕션 또는 안정적인 파싱이 필요할 때 활성화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tool_choice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;모델이 도구를 사용할 수 있는지, 특정 도구를 강제할지 여부&lt;/td&gt;
&lt;td&gt;&lt;code&gt;auto&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;호출 강제는 &lt;code&gt;required&lt;/code&gt;, 비활성화는 &lt;code&gt;none&lt;/code&gt;, 특정 함수 지정 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;엄격 모드는 잘못된 형식의 JSON을 줄이는 데 도움이 됩니다. 하지만 값이 비즈니스적으로 유효한지는 보장하지 않습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 다음 값은 스키마상 문자열이므로 유효할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Unknown City"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"celsius"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;하지만 실제 서비스에서는 지원하지 않는 지역일 수 있습니다. 따라서 별도의 비즈니스 검증과 오류 처리가 필요합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apidog에서 테스트하는 방법
&lt;/h2&gt;

&lt;p&gt;모델이 도구 호출을 반환하더라도, 실제 함수에 연결하기 전에 두 가지를 확인해야 합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;모델이 만든 &lt;code&gt;arguments&lt;/code&gt;가 함수 스키마와 일치하는가?&lt;/li&gt;
&lt;li&gt;함수가 호출할 하위 API가 예상대로 동작하는가?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;는 이 두 영역을 검증하는 데 사용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftl41vtt267jxp6mce0sh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftl41vtt267jxp6mce0sh.png" alt="Apidog API testing interface" width="799" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apidog가 직접 애플리케이션 함수를 실행하는 것은 아닙니다. 대신 함수 주변의 API 계약을 검증하고 모의 처리합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 도구 호출 인수 검증하기
&lt;/h3&gt;

&lt;p&gt;OpenAI 응답에서 &lt;code&gt;arguments&lt;/code&gt; 문자열을 추출합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Paris, France&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;unit&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;celsius&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이를 JSON으로 파싱하면 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paris, France"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"celsius"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apidog에서 이 JSON을 요청 본문처럼 다루고 다음을 검증할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;location&lt;/code&gt;이 존재하는지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;location&lt;/code&gt;이 문자열인지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unit&lt;/code&gt;이 허용된 enum 값인지&lt;/li&gt;
&lt;li&gt;필수 필드가 모두 있는지&lt;/li&gt;
&lt;li&gt;추가 필드가 없는지&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;필드 추출에는 &lt;a href="https://apidog.com/kr/blog/jsonpath-examples?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;JSONPath 표현식&lt;/a&gt;을 사용할 수 있습니다. 더 엄격한 구조 검사는 &lt;a href="https://apidog.com/kr/blog/how-to-validate-json-schema?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;JSON 스키마 유효성 검사&lt;/a&gt;를 적용하면 됩니다.&lt;/p&gt;

&lt;p&gt;권장 방식은 OpenAI 도구 정의에 사용한 스키마와 동일한 스키마를 Apidog 검증에도 반영하는 것입니다. 그러면 모델 출력과 함수 입력 계약을 같은 기준으로 확인할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 함수가 호출할 하위 API 모의 처리하기
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;get_weather&lt;/code&gt; 함수는 보통 외부 날씨 API 또는 내부 서비스를 호출합니다. 하지만 개발 중에는 다음 문제가 있을 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;실제 API가 아직 준비되지 않음&lt;/li&gt;
&lt;li&gt;호출 비용이 발생함&lt;/li&gt;
&lt;li&gt;속도 제한이 있음&lt;/li&gt;
&lt;li&gt;오류 응답을 재현하기 어려움&lt;/li&gt;
&lt;li&gt;테스트 데이터가 불안정함&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 경우 Apidog에서 &lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;모의 API&lt;/a&gt;를 만들고 함수가 해당 모의 API를 호출하도록 설정합니다.&lt;/p&gt;

&lt;p&gt;예를 들어 모의 응답은 다음처럼 구성할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paris, France"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"celsius"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cloudy"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;오류 케이스도 직접 만들 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RATE_LIMITED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Too many requests"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 하면 실제 API를 소모하지 않고도 다음을 테스트할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;정상 응답 처리&lt;/li&gt;
&lt;li&gt;타임아웃 처리&lt;/li&gt;
&lt;li&gt;429 응답 처리&lt;/li&gt;
&lt;li&gt;잘못된 응답 형식 처리&lt;/li&gt;
&lt;li&gt;빈 데이터 처리&lt;/li&gt;
&lt;li&gt;하위 API 장애 시 사용자 응답 처리&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;권장 워크플로우는 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OpenAI에서 도구 호출을 캡처합니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;arguments&lt;/code&gt;를 파싱합니다.&lt;/li&gt;
&lt;li&gt;Apidog에서 JSON 스키마로 인수를 검증합니다.&lt;/li&gt;
&lt;li&gt;함수가 호출할 하위 API를 Apidog 모의 API로 대체합니다.&lt;/li&gt;
&lt;li&gt;정상 응답과 오류 응답을 모두 테스트합니다.&lt;/li&gt;
&lt;li&gt;검증이 끝나면 실제 API로 전환합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문
&lt;/h2&gt;

&lt;h3&gt;
  
  
  함수 호출은 Chat Completions와 Responses API 모두에서 작동하나요?
&lt;/h3&gt;

&lt;p&gt;예. 두 엔드포인트 모두 함수 호출을 지원합니다.&lt;/p&gt;

&lt;p&gt;차이는 응답과 도구 정의의 형태입니다.&lt;/p&gt;

&lt;p&gt;Chat Completions는 함수를 &lt;code&gt;function&lt;/code&gt; 키 아래에 중첩하고, 응답에서 &lt;code&gt;tool_calls&lt;/code&gt;를 반환합니다.&lt;/p&gt;

&lt;p&gt;Responses API는 더 평탄한 도구 정의를 사용하고, &lt;code&gt;output&lt;/code&gt; 배열 안에 &lt;code&gt;function_call&lt;/code&gt; 항목을 반환합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  모델이 인수를 객체가 아니라 문자열로 반환하는 이유는 무엇인가요?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;arguments&lt;/code&gt; 필드는 JSON으로 인코딩된 문자열입니다. 따라서 사용 전 반드시 파싱해야 합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;파싱 후에는 스키마 검증을 수행하는 것이 안전합니다. &lt;a href="https://apidog.com/kr/blog/how-to-validate-json-schema?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;JSON 스키마 유효성 검사&lt;/a&gt;를 적용하면 잘못된 페이로드가 실제 함수에 도달하기 전에 차단할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  엄격 모드는 함수 성공을 보장하나요?
&lt;/h3&gt;

&lt;p&gt;아니요. 엄격 모드는 인수 구조가 JSON 스키마와 일치하도록 돕습니다.&lt;/p&gt;

&lt;p&gt;하지만 다음은 보장하지 않습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;외부 API 성공&lt;/li&gt;
&lt;li&gt;비즈니스 규칙 충족&lt;/li&gt;
&lt;li&gt;사용자가 요청한 값의 실제 존재 여부&lt;/li&gt;
&lt;li&gt;함수 내부 로직 성공&lt;/li&gt;
&lt;li&gt;네트워크 장애 방지&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;따라서 엄격 모드와 별도로 값 검증, 예외 처리, 재시도, 타임아웃 처리를 구현해야 합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apidog가 실제 함수를 실행할 수 있나요?
&lt;/h3&gt;

&lt;p&gt;아니요. Apidog는 애플리케이션 함수를 대신 실행하지 않습니다.&lt;/p&gt;

&lt;p&gt;Apidog의 역할은 다음에 가깝습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;모델이 생성한 인수 구조 검증&lt;/li&gt;
&lt;li&gt;JSON 스키마 기반 계약 확인&lt;/li&gt;
&lt;li&gt;함수가 의존하는 API 모의 처리&lt;/li&gt;
&lt;li&gt;정상 및 오류 응답 테스트&lt;/li&gt;
&lt;li&gt;API 문서와 테스트 컬렉션 관리&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;실제 함수 실행은 여전히 애플리케이션 코드가 담당합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;OpenAI 함수 호출 구현의 핵심 루프는 단순합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;도구를 명확하게 정의합니다.&lt;/li&gt;
&lt;li&gt;사용자 메시지와 함께 도구를 모델에 전달합니다.&lt;/li&gt;
&lt;li&gt;모델이 반환한 &lt;code&gt;tool_calls&lt;/code&gt; 또는 &lt;code&gt;function_call&lt;/code&gt;을 읽습니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;arguments&lt;/code&gt;를 JSON으로 파싱합니다.&lt;/li&gt;
&lt;li&gt;허용된 함수만 실행합니다.&lt;/li&gt;
&lt;li&gt;실행 결과를 모델에 다시 전달합니다.&lt;/li&gt;
&lt;li&gt;모델이 최종 응답을 생성합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;프로덕션에 가까워질수록 다음 설정을 추가하세요.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;strict: true&lt;/code&gt;로 인수 구조 안정화&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;additionalProperties: false&lt;/code&gt;로 예상 외 필드 차단&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parallel_tool_calls&lt;/code&gt;로 병렬 실행 제어&lt;/li&gt;
&lt;li&gt;스키마 검증으로 잘못된 입력 차단&lt;/li&gt;
&lt;li&gt;Apidog 모의 API로 하위 API 의존성 테스트&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;테스트 측면을 강화하고 싶다면 &lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 다운로드&lt;/a&gt;하여 도구 호출 인수를 스키마에 대해 확인하고, 함수가 의존하는 API를 한 곳에서 모의 처리해 보세요.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>OpenAI Responses API 사용법</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Fri, 26 Jun 2026 03:15:23 +0000</pubDate>
      <link>https://dev.to/rihpig/openai-responses-api-sayongbeob-1gb6</link>
      <guid>https://dev.to/rihpig/openai-responses-api-sayongbeob-1gb6</guid>
      <description>&lt;p&gt;이 가이드는 OpenAI Responses API를 처음부터 끝까지 구현하는 방법을 다룹니다. 완료하면 &lt;code&gt;POST /v1/responses&lt;/code&gt;로 요청을 보내고, 중첩된 &lt;code&gt;output&lt;/code&gt;을 파싱하고, 내장 도구를 활성화하고, 호출 간 대화 상태를 유지하며, &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;에서 API 계약을 테스트할 수 있습니다. Responses API는 OpenAI의 새로운 모델 출력 생성 인터페이스이며, &lt;a href="https://developers.openai.com/api/docs/guides/migrate-to-responses" rel="noopener noreferrer"&gt;공식 Responses 가이드&lt;/a&gt;는 OpenAI가 왜 새 프로젝트에 이 API를 권장하는지 설명합니다. 기존 엔드포인트를 테스트 중이라면 &lt;a href="https://apidog.com/kr/blog/how-to-test-chatgpt-api-with-apidog?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;ChatGPT API 테스트 가이드&lt;/a&gt;의 워크플로를 대부분 재사용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;오늘 Apidog를 사용해 보세요&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  먼저 필요한 것
&lt;/h2&gt;

&lt;p&gt;요청을 보내기 전에 다음을 준비하세요.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;현재 범용 모델에 접근할 수 있는 OpenAI API 키&lt;/li&gt;
&lt;li&gt;계정에서 실제로 호출 가능한 모델 이름&lt;/li&gt;
&lt;li&gt;원시 HTTP 요청을 보내고 JSON 응답을 확인할 도구

&lt;ul&gt;
&lt;li&gt;첫 호출: &lt;code&gt;curl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;반복 테스트, 어설션, 목업: &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;API 키는 환경 변수에 저장하세요. 명령어, 문서, 공유 컬렉션에 직접 붙여넣지 않는 것이 좋습니다.&lt;/p&gt;

&lt;p&gt;Responses API의 핵심 엔드포인트는 하나입니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /v1/responses
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://platform.openai.com/docs/api-reference/responses" rel="noopener noreferrer"&gt;API 레퍼런스&lt;/a&gt;에 따르면 이 엔드포인트는 모델 이름과 &lt;code&gt;input&lt;/code&gt;을 받아 응답 객체를 반환합니다. 응답에는 일반 텍스트뿐 아니라 함수 호출, 웹 검색, 파일 검색 같은 OpenAI 호스팅 도구 실행 결과가 포함될 수 있습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 한 번의 호출에서 모델은 다음을 수행할 수 있습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;사용자 입력을 해석합니다.&lt;/li&gt;
&lt;li&gt;필요한 경우 웹 검색을 실행합니다.&lt;/li&gt;
&lt;li&gt;검색 결과를 읽습니다.&lt;/li&gt;
&lt;li&gt;최종 답변을 작성합니다.&lt;/li&gt;
&lt;li&gt;각 단계를 &lt;code&gt;output&lt;/code&gt; 배열에 기록합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Responses API가 일반 텍스트 생성 API와 다른 점은 크게 두 가지입니다.&lt;/p&gt;

&lt;p&gt;첫째, 내장 도구를 서버 측에서 실행할 수 있습니다. 자체 검색 파이프라인이나 코드 실행 샌드박스를 직접 붙이지 않아도 됩니다.&lt;/p&gt;

&lt;p&gt;둘째, 기본적으로 상태를 가질 수 있습니다. 각 응답은 &lt;code&gt;id&lt;/code&gt;를 가지며, 다음 요청에서 이 값을 &lt;code&gt;previous_response_id&lt;/code&gt;로 전달하면 OpenAI가 이전 대화 컨텍스트를 이어서 처리합니다.&lt;/p&gt;

&lt;p&gt;OpenAI는 Responses API를 Chat Completions의 진화로 설명하며, 새로운 프로젝트에는 Responses API 사용을 권장합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chat Completions와 다른 점
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;POST /v1/chat/completions&lt;/code&gt;를 사용해 봤다면 가장 큰 차이는 요청/응답 형태와 상태 관리 방식입니다.&lt;/p&gt;

&lt;p&gt;Chat Completions는 &lt;code&gt;messages&lt;/code&gt; 배열을 받고 &lt;code&gt;choices&lt;/code&gt;를 반환합니다. 대화 기록은 클라이언트가 직접 관리해야 하며, 매 호출마다 이전 메시지를 다시 보내는 방식이 일반적입니다.&lt;/p&gt;

&lt;p&gt;Responses API는 &lt;code&gt;input&lt;/code&gt;을 받고 &lt;code&gt;output&lt;/code&gt;을 반환합니다. &lt;code&gt;input&lt;/code&gt;은 문자열이거나 유형화된 항목 목록일 수 있습니다. 또한 &lt;code&gt;store&lt;/code&gt;와 &lt;code&gt;previous_response_id&lt;/code&gt;를 통해 서버 측 대화 상태를 이어갈 수 있습니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;측면&lt;/th&gt;
&lt;th&gt;Chat Completions&lt;/th&gt;
&lt;th&gt;Responses API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;엔드포인트&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/chat/completions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/responses&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;요청 본문&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;messages&lt;/code&gt; 배열&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;input&lt;/code&gt; + &lt;code&gt;instructions&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;출력 형태&lt;/td&gt;
&lt;td&gt;&lt;code&gt;choices[].message&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;유형화된 항목 목록인 &lt;code&gt;output&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대화 상태&lt;/td&gt;
&lt;td&gt;전체 기록을 다시 전송&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;store&lt;/code&gt; + &lt;code&gt;previous_response_id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;내장 도구&lt;/td&gt;
&lt;td&gt;직접 구현&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;web_search&lt;/code&gt;, &lt;code&gt;file_search&lt;/code&gt;, &lt;code&gt;code_interpreter&lt;/code&gt; 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;상태&lt;/td&gt;
&lt;td&gt;계속 지원됨&lt;/td&gt;
&lt;td&gt;새 프로젝트에 권장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Chat Completions는 폐기 예정이 아닙니다. 따라서 기존 통합을 한 번에 모두 바꿀 필요는 없습니다. 사용자 흐름 단위로 점진적으로 마이그레이션할 수 있습니다.&lt;/p&gt;

&lt;p&gt;반면 Assistants API는 폐기 예정입니다. OpenAI는 2025년 8월 26일 폐기를 발표했고, 2026년 8월 26일을 서비스 종료일로 명시했습니다. 새 에이전트형 작업은 Responses API에서 시작하는 것이 적절합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  첫 요청 보내기
&lt;/h2&gt;

&lt;p&gt;가장 작은 형태의 호출부터 시작합니다.&lt;/p&gt;

&lt;p&gt;먼저 API 키를 환경 변수로 설정하세요.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_api_key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;그다음 &lt;code&gt;POST /v1/responses&lt;/code&gt;를 호출합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.openai.com/v1/responses &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "gpt-5.5",
    "input": "Write one sentence describing what an API mock server does.",
    "instructions": "You are a concise technical writer. No marketing language.",
    "store": true
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;여기서 핵심 필드는 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;model&lt;/code&gt;: 호출할 모델 이름&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;input&lt;/code&gt;: 사용자 입력 또는 프롬프트&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;instructions&lt;/code&gt;: 시스템 수준의 지시&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;store&lt;/code&gt;: 응답을 저장해 이후 대화에 재사용할지 여부&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;모델 이름은 계정 권한에 따라 달라질 수 있습니다. 하드코딩하기 전에 OpenAI 대시보드에서 사용할 수 있는 모델을 확인하세요.&lt;/p&gt;

&lt;h2&gt;
  
  
  응답 읽기
&lt;/h2&gt;

&lt;p&gt;간략화된 응답은 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resp_abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"output_text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A mock server returns predefined API responses so clients can be developed and tested before the real backend exists."&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;텍스트는 최상위 필드에 있지 않습니다. 원시 HTTP 응답에서 실제 답변 텍스트는 다음 경로에 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output[0].content[0].text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;확인해야 할 주요 필드는 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt;: 다음 요청에서 &lt;code&gt;previous_response_id&lt;/code&gt;로 사용할 수 있는 응답 ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt;: 요청 처리 상태&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;output&lt;/code&gt;: 메시지, 도구 호출 등 유형화된 출력 항목&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;usage.total_tokens&lt;/code&gt;: 입력과 출력에 사용된 전체 토큰 수&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SDK에서는 여러 텍스트 항목을 합쳐주는 &lt;code&gt;output_text&lt;/code&gt; 같은 편의 접근자를 제공할 수 있습니다. 하지만 원시 HTTP JSON에서는 중첩된 &lt;code&gt;output[].content[].text&lt;/code&gt; 경로를 직접 읽어야 합니다.&lt;/p&gt;

&lt;p&gt;예를 들어 &lt;code&gt;jq&lt;/code&gt;로 텍스트만 추출하려면 다음처럼 처리할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.openai.com/v1/responses &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "gpt-5.5",
    "input": "Write one sentence describing what an API mock server does."
  }'&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.output[0].content[0].text'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  내장 도구 추가하기
&lt;/h2&gt;

&lt;p&gt;Responses API의 중요한 기능 중 하나는 OpenAI가 특정 도구를 대신 실행할 수 있다는 점입니다.&lt;/p&gt;

&lt;p&gt;도구는 &lt;code&gt;tools&lt;/code&gt; 배열에 선언합니다. 모델은 입력을 보고 도구 호출이 필요한지 결정합니다.&lt;/p&gt;

&lt;p&gt;사용 가능한 내장 도구 유형은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;web_search&lt;/code&gt;: 인용문을 포함한 실시간 인터넷 검색&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;file_search&lt;/code&gt;: 업로드한 파일 기반 검색&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;code_interpreter&lt;/code&gt;: 샌드박스에서 코드 실행 및 분석&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;computer_use&lt;/code&gt;: 컴퓨터 인터페이스 제어&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;image_generation&lt;/code&gt;: 인라인 이미지 생성&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예를 들어 웹 검색을 허용하려면 다음처럼 요청합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What changed in the latest OpenAPI release? Cite sources."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"web_search"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;모델이 도구를 사용하면 &lt;code&gt;output&lt;/code&gt; 배열에는 최종 메시지뿐 아니라 도구 실행 단계를 나타내는 항목도 포함됩니다.&lt;/p&gt;

&lt;p&gt;예를 들어 웹 검색을 실행했다면 &lt;code&gt;web_search_call&lt;/code&gt; 유형의 항목이 나타날 수 있습니다. 따라서 통합 테스트에서는 단순히 텍스트가 반환됐는지만 확인하지 말고, 필요한 도구가 실제로 호출됐는지도 확인하는 것이 좋습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  호출 간 대화 계속하기
&lt;/h2&gt;

&lt;p&gt;Responses API에서 상태 관리는 &lt;code&gt;store&lt;/code&gt;와 &lt;code&gt;previous_response_id&lt;/code&gt;로 처리합니다.&lt;/p&gt;

&lt;p&gt;기본적으로 &lt;code&gt;store&lt;/code&gt;는 &lt;code&gt;true&lt;/code&gt;입니다. 이 경우 OpenAI는 응답 객체를 저장하고, 이후 요청에서 참조할 수 있는 &lt;code&gt;id&lt;/code&gt;를 반환합니다.&lt;/p&gt;

&lt;p&gt;첫 요청에서 받은 응답 ID가 다음과 같다고 가정합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resp_abc123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이후 요청에서 &lt;code&gt;previous_response_id&lt;/code&gt;로 전달하면 이전 대화를 이어갈 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Now rewrite that for a non-technical audience."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"previous_response_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resp_abc123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 방식의 장점은 클라이언트가 전체 대화 기록을 매번 다시 보내지 않아도 된다는 점입니다.&lt;/p&gt;

&lt;p&gt;반대로 상태 비저장 방식이 필요하다면 &lt;code&gt;store&lt;/code&gt;를 &lt;code&gt;false&lt;/code&gt;로 설정하고 컨텍스트를 직접 관리하세요.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Summarize this text."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;실시간 음성 또는 오디오 기반 저지연 흐름은 다른 인터페이스를 사용합니다. 해당 사례는 &lt;a href="https://apidog.com/kr/blog/gpt-realtime-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT 실시간 API 가이드&lt;/a&gt;에서 다룹니다. 다단계 에이전트를 구성하는 경우에는 &lt;a href="https://apidog.com/kr/blog/how-to-use-openai-agents-sdk?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenAI Agents SDK&lt;/a&gt; 패턴과도 연결됩니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apidog에서 테스트하는 방법
&lt;/h2&gt;

&lt;p&gt;Apidog는 API 테스트, 설계, 목업 플랫폼입니다. OpenAI SDK나 코드 라이브러리가 아니므로 Python 코드를 작성하지 않습니다.&lt;/p&gt;

&lt;p&gt;대신 Apidog에서 다음 작업을 수행합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;/v1/responses&lt;/code&gt;에 대한 원시 HTTP 요청을 만듭니다.&lt;/li&gt;
&lt;li&gt;환경 변수로 API 키를 관리합니다.&lt;/li&gt;
&lt;li&gt;요청을 전송합니다.&lt;/li&gt;
&lt;li&gt;반환된 JSON 구조를 확인합니다.&lt;/li&gt;
&lt;li&gt;응답 필드에 어설션을 추가합니다.&lt;/li&gt;
&lt;li&gt;필요하면 응답을 목업으로 저장합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이 방식은 실제 앱이 의존하는 API 계약을 직접 검증하는 데 적합합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-468.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-468.png" alt="" width="799" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 환경 변수에 API 키 저장하기
&lt;/h3&gt;

&lt;p&gt;Apidog에서 새 환경을 만듭니다.&lt;/p&gt;

&lt;p&gt;예:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenAI Prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;그다음 환경 변수로 API 키를 추가합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPENAI_API_KEY=your_api_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;요청에는 실제 키를 직접 넣지 말고 변수를 사용합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer {{OPENAI_API_KEY}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 하면 공유 컬렉션이나 문서에 비밀 값이 노출되는 것을 피할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;/v1/responses&lt;/code&gt; 요청 만들기
&lt;/h3&gt;

&lt;p&gt;Apidog에서 새 &lt;code&gt;POST&lt;/code&gt; 요청을 생성합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://api.openai.com/v1/responses
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;헤더를 추가합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer {{OPENAI_API_KEY}}
Content-Type: application/json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;본문은 JSON으로 설정합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Write one sentence describing what an API mock server does."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instructions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are a concise technical writer. No marketing language."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;전송하면 Apidog에서 전체 응답 객체와 중첩된 &lt;code&gt;output&lt;/code&gt; 배열을 확인할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 응답 필드에 어설션 추가하기
&lt;/h3&gt;

&lt;p&gt;HTTP 200만 확인하는 것은 충분하지 않습니다. 앱이 기대하는 JSON 형태가 유지되는지 확인해야 합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/v1/responses&lt;/code&gt; 응답에서 유용한 어설션은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt;가 &lt;code&gt;completed&lt;/code&gt;와 같음&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;output[0].content[0].text&lt;/code&gt;가 존재함&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;output[0].content[0].text&lt;/code&gt;가 빈 문자열이 아님&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;usage.total_tokens&lt;/code&gt;가 0보다 큼&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tools&lt;/code&gt;를 보낸 경우 &lt;code&gt;output&lt;/code&gt; 항목 중 &lt;code&gt;type&lt;/code&gt;이 &lt;code&gt;web_search_call&lt;/code&gt;인 항목이 존재함&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apidog의 시각적 어설션 빌더를 사용하면 테스트 스크립트를 직접 작성하지 않고도 JSON 경로 기반 검사를 추가할 수 있습니다. 더 구체적인 구성 방식은 &lt;a href="https://apidog.com/kr/blog/api-test-case-example?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 테스트 케이스 템플릿&lt;/a&gt;을 참고하세요.&lt;/p&gt;

&lt;p&gt;요청을 컬렉션에 저장하면 반복 가능한 테스트로 사용할 수 있고, CI에서 실행할 수도 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. 내장 도구 호출 테스트하기
&lt;/h3&gt;

&lt;p&gt;웹 검색 도구를 테스트하려면 본문에 &lt;code&gt;tools&lt;/code&gt; 배열을 추가합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What changed in the latest OpenAPI release? Cite sources."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"web_search"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;응답에서 다음을 확인하세요.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output[*].type
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;도구가 실행되었다면 &lt;code&gt;web_search_call&lt;/code&gt; 같은 항목이 포함됩니다.&lt;/p&gt;

&lt;p&gt;테스트 기준은 다음처럼 잡을 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;최종 답변 메시지가 존재한다.&lt;/li&gt;
&lt;li&gt;도구 호출 항목이 존재한다.&lt;/li&gt;
&lt;li&gt;도구 호출 후 생성된 텍스트가 비어 있지 않다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이렇게 하면 모델이 단순히 답변을 생성했는지뿐 아니라, 기대한 방식으로 도구를 사용했는지도 검증할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 오프라인 개발을 위한 응답 목업 만들기
&lt;/h3&gt;

&lt;p&gt;OpenAI 호출은 비용이 발생하고 네트워크 접근이 필요합니다. UI 개발이나 CI 테스트에서 매번 실제 API를 호출하면 느리고 불안정할 수 있습니다.&lt;/p&gt;

&lt;p&gt;Apidog의 목업 기능을 사용하면 대표적인 &lt;code&gt;/v1/responses&lt;/code&gt; 응답을 저장하고, 프런트엔드나 테스트 환경이 Apidog 목업 URL을 바라보게 할 수 있습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 다음과 같은 응답 형태를 목업으로 저장합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resp_mock123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"output_text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A mock server returns predefined API responses."&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이후 앱은 실제 OpenAI API 대신 Apidog 목업 URL에서 동일한 JSON 구조를 받을 수 있습니다.&lt;/p&gt;

&lt;p&gt;이 접근의 장점은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;토큰 비용 없이 UI 개발 가능&lt;/li&gt;
&lt;li&gt;네트워크 장애와 무관하게 테스트 가능&lt;/li&gt;
&lt;li&gt;응답 구조를 고정해 결정론적 테스트 가능&lt;/li&gt;
&lt;li&gt;실제 API 계약 변경 시 목업만 업데이트 가능&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;일반적인 목업 방식은 &lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;목업 API 설명서&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;

&lt;p&gt;실제 엔드포인트 테스트와 목업은 목적이 다릅니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;실제 엔드포인트 테스트: OpenAI API 계약 확인&lt;/li&gt;
&lt;li&gt;목업: 빠르고 오프라인이며 결정론적인 개발 환경 제공&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;둘 다 같은 Apidog 프로젝트에서 관리할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Responses API가 Chat Completions를 대체하나요?
&lt;/h3&gt;

&lt;p&gt;강제 대체는 아닙니다. OpenAI는 Responses API를 Chat Completions의 진화로 설명하며 새 프로젝트에 권장하지만, Chat Completions는 폐기 날짜 없이 계속 지원됩니다.&lt;/p&gt;

&lt;p&gt;기존 앱은 한 번에 모두 옮기기보다 사용자 흐름 단위로 점진적으로 마이그레이션하는 것이 안전합니다.&lt;/p&gt;

&lt;p&gt;Assistants API는 폐기 예정이며, 2026년에 서비스 종료될 예정입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;store&lt;/code&gt;와 &lt;code&gt;previous_response_id&lt;/code&gt;의 차이는 무엇인가요?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;store&lt;/code&gt;는 OpenAI가 응답 객체를 저장할지 여부를 제어합니다. 기본값은 &lt;code&gt;true&lt;/code&gt;이며, 저장된 응답은 이후 대화 상태를 이어가는 데 사용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;previous_response_id&lt;/code&gt;는 새 요청을 이전 응답에 연결하는 필드입니다.&lt;/p&gt;

&lt;p&gt;상태 저장 대화를 만들려면 다음처럼 사용합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Continue from the previous answer."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"previous_response_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resp_abc123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;상태 비저장 동작이 필요하면 다음처럼 설정합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer without storing this response."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  어떤 모델이 Responses API를 지원하나요?
&lt;/h3&gt;

&lt;p&gt;OpenAI의 현재 범용 모델은 Responses API와 작동하도록 설계되었지만, 사용 가능 여부는 계정과 모델 권한에 따라 달라집니다.&lt;/p&gt;

&lt;p&gt;권장 절차는 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OpenAI 대시보드에서 사용 가능한 모델을 확인합니다.&lt;/li&gt;
&lt;li&gt;모델 이름을 요청 본문에 넣습니다.&lt;/li&gt;
&lt;li&gt;Apidog 또는 &lt;code&gt;curl&lt;/code&gt;로 테스트 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;응답의 &lt;code&gt;model&lt;/code&gt; 필드를 확인합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;모델 이름을 코드에 하드코딩하기 전에 실제 키로 호출 가능한지 검증하세요.&lt;/p&gt;

&lt;h3&gt;
  
  
  코드 작성 없이 내장 도구를 테스트할 수 있나요?
&lt;/h3&gt;

&lt;p&gt;네. Apidog에서 JSON 본문에 &lt;code&gt;tools&lt;/code&gt; 배열을 추가하고 요청을 보내면 됩니다.&lt;/p&gt;

&lt;p&gt;예:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Search the web and summarize the result."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"web_search"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;그다음 &lt;code&gt;output&lt;/code&gt; 배열에 도구 호출 항목이 나타나는지 어설션합니다.&lt;/p&gt;

&lt;p&gt;예:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output[*].type contains web_search_call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SDK 없이 HTTP 요청만으로 OpenAI의 도구 호출 동작을 확인할 수 있습니다. 에이전트 도구 호출을 더 넓은 범위에서 테스트하려면 &lt;a href="https://apidog.com/kr/blog/api-test-collections-generation-openapi-specs?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenAPI 스펙에서 API 테스트 컬렉션을 생성하는 방법&lt;/a&gt;을 참고하세요.&lt;/p&gt;

&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;Responses API의 기본 구현 흐름은 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;POST /v1/responses&lt;/code&gt;로 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;input&lt;/code&gt;과 &lt;code&gt;instructions&lt;/code&gt;로 모델 동작을 제어합니다.&lt;/li&gt;
&lt;li&gt;응답의 &lt;code&gt;output[0].content[0].text&lt;/code&gt;에서 텍스트를 읽습니다.&lt;/li&gt;
&lt;li&gt;검색이나 코드 실행이 필요하면 &lt;code&gt;tools&lt;/code&gt; 배열을 추가합니다.&lt;/li&gt;
&lt;li&gt;대화를 이어가려면 이전 응답의 &lt;code&gt;id&lt;/code&gt;를 &lt;code&gt;previous_response_id&lt;/code&gt;로 전달합니다.&lt;/li&gt;
&lt;li&gt;상태 비저장이 필요하면 &lt;code&gt;store: false&lt;/code&gt;를 사용합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Responses API는 Chat Completions와 응답 형태가 다릅니다. 따라서 이전 API의 구조를 가정하지 말고 실제 JSON 계약을 직접 확인하는 것이 안전합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;를 사용하면 하나의 프로젝트에서 요청을 만들고, API 키를 환경 변수로 관리하고, 중첩된 &lt;code&gt;output&lt;/code&gt; 필드에 어설션을 추가하고, 오프라인 개발을 위한 응답 목업까지 구성할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 다운로드&lt;/a&gt;한 뒤 &lt;code&gt;/v1/responses&lt;/code&gt; 요청을 테스트 컬렉션으로 저장해 통합이 실제로 어떤 응답을 받는지 검증하세요.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>OpenAI 구조화된 출력 활용 방법</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Fri, 26 Jun 2026 03:12:17 +0000</pubDate>
      <link>https://dev.to/rihpig/openai-gujohwadoen-culryeog-hwalyong-bangbeob-3cje</link>
      <guid>https://dev.to/rihpig/openai-gujohwadoen-culryeog-hwalyong-bangbeob-3cje</guid>
      <description>&lt;p&gt;이 가이드를 따라 하면 OpenAI 구조화된 출력을 코드에서 호출하고, JSON Schema와 &lt;code&gt;strict: true&lt;/code&gt;로 응답 형식을 고정하며, Apidog에서 요청/검증/목업까지 테스트 컬렉션으로 관리할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;오늘 Apidog를 사용해 보세요&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  시작하기 전에 필요한 것
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developers.openai.com/api/docs/guides/structured-outputs" rel="noopener noreferrer"&gt;구조화된 출력&lt;/a&gt;은 모델 응답이 제공한 JSON Schema를 따르도록 생성 결과를 제한합니다. &lt;code&gt;strict: true&lt;/code&gt;와 함께 스키마를 전달하면 필수 키, 타입, enum 값이 스키마와 일치하는 응답을 받을 수 있습니다.&lt;/p&gt;

&lt;p&gt;준비물은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;OPENAI_API_KEY&lt;/code&gt;로 설정된 OpenAI API 키&lt;/li&gt;
&lt;li&gt;엄격한 스키마 강제를 지원하는 모델&lt;/li&gt;
&lt;li&gt;원하는 응답 형식을 설명하는 JSON Schema&lt;/li&gt;
&lt;li&gt;응답 계약을 검증할 API 테스트 환경&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  올바른 모델 선택
&lt;/h2&gt;

&lt;p&gt;구조화된 출력은 GPT-4o 제품군부터 GPT-5 시리즈까지 OpenAI의 최신 모델에서 사용할 수 있습니다. OpenAI 문서는 현재 최신 플래그십 모델(&lt;code&gt;gpt-5.5&lt;/code&gt;, 이 글 작성 시점 기준)에서 새 프로젝트를 시작하는 것을 권장합니다.&lt;/p&gt;

&lt;p&gt;이전 모델과 &lt;code&gt;gpt-3.5&lt;/code&gt; 시대 모델은 JSON 모드를 지원하지만, 엄격한 스키마 강제는 지원하지 않습니다. &lt;code&gt;strict: true&lt;/code&gt;에 의존한다면 배포 전에 사용하려는 정확한 모델 ID가 구조화된 출력을 지원하는지 확인하십시오.&lt;/p&gt;

&lt;p&gt;OpenAI에는 혼동하기 쉬운 두 기능이 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  JSON 모드
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response_format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json_object"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JSON 모드는 출력이 &lt;strong&gt;구문적으로 유효한 JSON&lt;/strong&gt;임을 보장합니다. 하지만 필드, 타입, 필수 키, enum 값은 보장하지 않습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  구조화된 출력
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response_format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json_schema"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"json_schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;구조화된 출력은 유효한 JSON을 생성하고, 동시에 스키마 준수까지 강제합니다. 새 작업에는 일반적으로 구조화된 출력을 사용하는 것이 더 안전합니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;JSON 모드&lt;/th&gt;
&lt;th&gt;구조화된 출력 (엄격 모드)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;매개변수&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response_format: {"type":"json_object"}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;response_format&lt;/code&gt;에 &lt;code&gt;type: "json_schema"&lt;/code&gt;, &lt;code&gt;strict: true&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;유효한 JSON&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스키마 일치 여부&lt;/td&gt;
&lt;td&gt;아니요&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;필수 필드 강제 여부&lt;/td&gt;
&lt;td&gt;아니요&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;타입 및 열거형 강제 여부&lt;/td&gt;
&lt;td&gt;아니요&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;하위에서 여전히 검증 필요&lt;/td&gt;
&lt;td&gt;항상&lt;/td&gt;
&lt;td&gt;권장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;API 참고 사항:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chat Completions API는 &lt;code&gt;response_format&lt;/code&gt;을 사용합니다.&lt;/li&gt;
&lt;li&gt;Responses API는 &lt;code&gt;text.format&lt;/code&gt; 아래에 &lt;code&gt;type: "json_schema"&lt;/code&gt;를 사용합니다.&lt;/li&gt;
&lt;li&gt;스키마 규칙은 동일하지만 필드 경로가 다르므로, 사용하는 엔드포인트의 최신 문서를 확인하십시오.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  첫 번째 요청 보내기
&lt;/h2&gt;

&lt;p&gt;예제로 지원 티켓을 구조화된 레코드로 추출해 보겠습니다.&lt;/p&gt;

&lt;p&gt;다음 요청은 사용자의 자연어 문의를 &lt;code&gt;support_ticket&lt;/code&gt; 스키마에 맞는 JSON으로 반환하도록 강제합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.openai.com/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "gpt-5.5",
    "messages": [
      {
        "role": "system",
        "content": "Extract the ticket into the schema."
      },
      {
        "role": "user",
        "content": "My checkout 500s every time I use a saved card. Started today. Account: acct_8842."
      }
    ],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "support_ticket",
        "strict": true,
        "schema": {
          "type": "object",
          "properties": {
            "summary": {
              "type": "string"
            },
            "category": {
              "type": "string",
              "enum": ["billing", "bug", "account", "other"]
            },
            "severity": {
              "type": "integer"
            },
            "account_id": {
              "anyOf": [
                { "type": "string" },
                { "type": "null" }
              ]
            }
          },
          "required": ["summary", "category", "severity", "account_id"],
          "additionalProperties": false
        }
      }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;핵심 설정은 다음 세 가지입니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json_schema"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;type: "json_schema"&lt;/code&gt;: 구조화된 출력 사용&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strict: true&lt;/code&gt;: 스키마 준수 강제&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;additionalProperties: false&lt;/code&gt;: 정의되지 않은 키 생성 차단&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  응답 읽기
&lt;/h2&gt;

&lt;p&gt;모델은 스키마와 일치하는 JSON 문자열을 &lt;code&gt;content&lt;/code&gt;로 반환합니다.&lt;/p&gt;

&lt;p&gt;예상 응답 예시는 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Checkout returns HTTP 500 when paying with a saved card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_8842"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;account_id&lt;/code&gt;는 다음처럼 &lt;code&gt;string&lt;/code&gt; 또는 &lt;code&gt;null&lt;/code&gt;을 허용합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"anyOf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"null"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;구조화된 출력에서는 일반적인 의미의 선택적 필드가 없습니다. 키는 항상 존재해야 하며, 값이 없을 수 있는 필드는 &lt;code&gt;null&lt;/code&gt; 허용으로 모델링합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  스키마를 지원되는 하위 집합 안에 유지하기
&lt;/h2&gt;

&lt;p&gt;구조화된 출력은 &lt;a href="https://json-schema.org/" rel="noopener noreferrer"&gt;JSON Schema&lt;/a&gt; 전체가 아니라 하위 집합을 사용합니다. 스키마를 작성할 때 다음 규칙을 지키십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 루트는 객체여야 합니다
&lt;/h3&gt;

&lt;p&gt;최상위 레벨은 배열이나 문자열이 될 수 없습니다.&lt;/p&gt;

&lt;p&gt;잘못된 예:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;권장 방식:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. 모든 속성은 &lt;code&gt;required&lt;/code&gt;에 포함되어야 합니다
&lt;/h3&gt;

&lt;p&gt;구조화된 출력에서는 모든 속성이 &lt;code&gt;required&lt;/code&gt;에 있어야 합니다.&lt;/p&gt;

&lt;p&gt;선택적 값을 표현하려면 &lt;code&gt;null&lt;/code&gt;을 허용하십시오.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"anyOf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"null"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. 모든 객체에 &lt;code&gt;additionalProperties: false&lt;/code&gt;를 설정하십시오
&lt;/h3&gt;

&lt;p&gt;모델이 스키마에 없는 키를 추가하지 못하도록 모든 객체에 설정합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. 스키마 크기를 제한하십시오
&lt;/h3&gt;

&lt;p&gt;스키마에는 크기 제한이 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;약 100개의 객체 속성&lt;/li&gt;
&lt;li&gt;최대 5단계 중첩&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;깊고 넓은 스키마는 거부될 수 있으므로, 가능한 한 평평하게 설계하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 일부 검증 키워드는 보장되지 않습니다
&lt;/h3&gt;

&lt;p&gt;다음과 같은 유효성 검사 전용 키워드는 모델이 보장하지 않을 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pattern&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;format&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;minLength&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;minimum&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예를 들어 이메일 형식, 정규식, 숫자 범위 같은 비즈니스 규칙은 응답을 받은 뒤 애플리케이션 코드나 테스트에서 다시 검증해야 합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/kr/blog/api-parameters-json-schema?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;oneOf/anyOf/allOf로 선택적 또는 유니온 필드를 모델링&lt;/a&gt;해 본 적이 있다면 같은 방식으로 접근하면 됩니다. 스키마는 구조를 제한하고, 실제 값의 의미는 별도로 검증합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. 첫 호출은 느릴 수 있습니다
&lt;/h3&gt;

&lt;p&gt;새 스키마로 첫 요청을 보내면 스키마 컴파일 시간이 필요합니다. 일반적으로 몇 초가 걸리며, 복잡한 스키마는 최대 1분까지 걸릴 수 있습니다. 이후에는 캐시되어 더 빨라집니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  거부 및 잘림 처리
&lt;/h2&gt;

&lt;p&gt;구조화된 출력에서도 항상 &lt;code&gt;content&lt;/code&gt;에 스키마 형태의 JSON이 들어오는 것은 아닙니다.&lt;/p&gt;

&lt;p&gt;모델이 안전하지 않은 요청을 거부하면 메시지에 &lt;code&gt;refusal&lt;/code&gt; 필드가 포함될 수 있습니다. JSON을 파싱하기 전에 먼저 분기하십시오.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refusal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;handle_refusal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refusal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 방식은 응답 텍스트에서 사과 문구를 검색하는 것보다 안정적입니다.&lt;/p&gt;

&lt;p&gt;또한 다음 상황에서는 응답이 스키마를 만족하지 못할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;max_tokens&lt;/code&gt;에 도달해 JSON이 중간에서 잘림&lt;/li&gt;
&lt;li&gt;구조화된 출력이 지원하지 않는 병렬 도구 호출 사용&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;도구 호출과 함께 사용할 때는 다음처럼 병렬 호출을 비활성화하십시오.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parallel_tool_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Apidog에서 테스트하는 방법
&lt;/h2&gt;

&lt;p&gt;엄격 모드는 생성 시점에 스키마를 강제합니다. 하지만 테스트를 생략해도 된다는 뜻은 아닙니다.&lt;/p&gt;

&lt;p&gt;다음 변경은 언제든 발생할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;모델 변경&lt;/li&gt;
&lt;li&gt;프롬프트 변경&lt;/li&gt;
&lt;li&gt;스키마 변경&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;required&lt;/code&gt; 배열 수정&lt;/li&gt;
&lt;li&gt;거부 처리 경로 변경&lt;/li&gt;
&lt;li&gt;하위 서비스의 계약 변경&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이런 변경을 CI에서 감지하려면 API 응답이 계약과 일치하는지 테스트해야 합니다. 이때 &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;를 사용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-467.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-467.png" alt="" width="799" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;역할을 명확히 나누면 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI 구조화된 출력: 스키마에 맞는 JSON 생성&lt;/li&gt;
&lt;li&gt;Apidog: 받은 응답이 예상 스키마와 일치하는지 검증&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apidog가 모델에 스키마를 강제하는 것은 아닙니다. Apidog는 응답을 검증하고, 계약 위반을 프로덕션이 아니라 테스트 단계에서 발견하도록 도와줍니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apidog 테스트 워크플로
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Chat Completions 요청 만들기
&lt;/h3&gt;

&lt;p&gt;Apidog에서 OpenAI Chat Completions 요청을 생성합니다.&lt;/p&gt;

&lt;p&gt;요청 본문에는 앞에서 사용한 &lt;code&gt;response_format&lt;/code&gt; 블록을 그대로 넣습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"system"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Extract the ticket into the schema."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My checkout 500s every time I use a saved card. Started today. Account: acct_8842."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response_format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json_schema"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"json_schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support_ticket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"billing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"other"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"anyOf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"null"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;반복 실행할 수 있도록 컬렉션에 저장하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 응답 스키마 검증 추가하기
&lt;/h3&gt;

&lt;p&gt;Apidog에서 응답 검증을 추가합니다.&lt;/p&gt;

&lt;p&gt;검증 대상은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;summary&lt;/code&gt;가 문자열인지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;category&lt;/code&gt;가 &lt;code&gt;billing&lt;/code&gt;, &lt;code&gt;bug&lt;/code&gt;, &lt;code&gt;account&lt;/code&gt;, &lt;code&gt;other&lt;/code&gt; 중 하나인지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;severity&lt;/code&gt;가 정수인지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;account_id&lt;/code&gt;가 문자열 또는 &lt;code&gt;null&lt;/code&gt;인지&lt;/li&gt;
&lt;li&gt;스키마에 없는 추가 필드가 없는지&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apidog는 &lt;a href="https://apidog.com/kr/blog/how-to-validate-json-schema?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;응답을 JSON Schema에 대해 검증&lt;/a&gt;할 수 있으므로, OpenAI에 전달한 스키마와 동일한 스키마를 테스트에도 사용하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. CI에서 컬렉션 실행하기
&lt;/h3&gt;

&lt;p&gt;모델, 프롬프트, 스키마가 변경될 때마다 컬렉션을 실행하도록 파이프라인에 추가합니다.&lt;/p&gt;

&lt;p&gt;목표는 단순합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;스키마가 깨지면 테스트 실패&lt;/li&gt;
&lt;li&gt;응답 계약이 바뀌면 빌드 실패&lt;/li&gt;
&lt;li&gt;하위 서비스가 잘못된 페이로드를 받기 전에 감지&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. 목업 API로 하위 소비자 테스트하기
&lt;/h3&gt;

&lt;p&gt;실제 OpenAI 호출을 붙이기 전에도 하위 소비자를 개발할 수 있습니다.&lt;/p&gt;

&lt;p&gt;Apidog에서 &lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;목업 API&lt;/a&gt;를 설정하고, 동일한 스키마를 만족하는 샘플 응답을 반환하도록 구성하십시오.&lt;/p&gt;

&lt;p&gt;이 방식은 다음 상황에서 유용합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;토큰을 사용하지 않고 프론트엔드 개발&lt;/li&gt;
&lt;li&gt;OpenAI 호출이 준비되기 전 통합 테스트&lt;/li&gt;
&lt;li&gt;CI에서 안정적인 테스트 데이터 사용&lt;/li&gt;
&lt;li&gt;하위 서비스의 계약 검증&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;동일한 스키마를 따르는 목업을 먼저 사용하고, 준비가 되면 실제 OpenAI 호출로 교체하면 됩니다. &lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 다운로드&lt;/a&gt;하면 요청, 어설션, 목업을 한 곳에서 구성할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문
&lt;/h2&gt;

&lt;h3&gt;
  
  
  구조화된 출력이 있으면 JSON 모드는 사용 중단되었나요?
&lt;/h3&gt;

&lt;p&gt;아니요. JSON 모드는 여전히 작동하며 유효한 JSON을 보장합니다.&lt;/p&gt;

&lt;p&gt;다만 스키마를 강제하지 않습니다. 새 코드에서는 &lt;code&gt;strict: true&lt;/code&gt;를 사용하는 구조화된 출력이 더 강력한 선택입니다.&lt;/p&gt;

&lt;p&gt;JSON 모드는 다음 경우에만 고려하십시오.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;사용하는 모델이 엄격 모드를 지원하지 않음&lt;/li&gt;
&lt;li&gt;고정된 응답 형태가 필요 없음&lt;/li&gt;
&lt;li&gt;직접 후처리 검증을 수행할 계획이 있음&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  스키마의 루트가 배열일 수 있나요?
&lt;/h3&gt;

&lt;p&gt;아니요. 최상위 레벨은 객체여야 합니다.&lt;/p&gt;

&lt;p&gt;배열이 필요하면 객체 속성으로 감싸십시오.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  필드를 선택 사항으로 만드는 방법은 무엇입니까?
&lt;/h3&gt;

&lt;p&gt;구조화된 출력에서는 모든 속성이 &lt;code&gt;required&lt;/code&gt;에 있어야 합니다.&lt;/p&gt;

&lt;p&gt;따라서 선택 사항은 “키가 없음”이 아니라 “값이 &lt;code&gt;null&lt;/code&gt;일 수 있음”으로 모델링합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"anyOf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"null"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;키는 항상 존재하고, 값만 &lt;code&gt;null&lt;/code&gt;이 될 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  엄격 모드에서는 유효성 검사를 완전히 건너뛰어도 되나요?
&lt;/h3&gt;

&lt;p&gt;형태 검사는 대부분 줄일 수 있습니다. 하지만 비즈니스 유효성 검사는 여전히 필요합니다.&lt;/p&gt;

&lt;p&gt;특히 다음은 별도로 확인하십시오.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;정규식 패턴&lt;/li&gt;
&lt;li&gt;이메일/URL 같은 포맷&lt;/li&gt;
&lt;li&gt;숫자 범위&lt;/li&gt;
&lt;li&gt;문자열 길이&lt;/li&gt;
&lt;li&gt;도메인별 허용 값&lt;/li&gt;
&lt;li&gt;거부 응답&lt;/li&gt;
&lt;li&gt;잘린 응답&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JSON Schema가 익숙하지 않다면 &lt;a href="https://apidog.com/kr/blog/what-is-json-schema?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;JSON Schema 입문서&lt;/a&gt;를 먼저 확인하고, 배포마다 검증을 실행하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  어떤 모델을 사용해야 합니까?
&lt;/h3&gt;

&lt;p&gt;구조화된 출력은 GPT-4o 및 이후 모델, GPT-5 시리즈에서 작동합니다. OpenAI 문서는 현재 플래그십 모델에 새 프로젝트를 집중하도록 안내합니다.&lt;/p&gt;

&lt;p&gt;엄격 모드 지원은 모델 버전별로 다를 수 있으므로, 사용하려는 정확한 모델 ID가 &lt;code&gt;strict: true&lt;/code&gt;를 지원하는지 확인한 뒤 의존하십시오.&lt;/p&gt;

&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;구현 흐름은 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;엄격 모드를 지원하는 모델을 선택합니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;response_format.type&lt;/code&gt;을 &lt;code&gt;json_schema&lt;/code&gt;로 설정합니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strict: true&lt;/code&gt;를 활성화합니다.&lt;/li&gt;
&lt;li&gt;루트 객체, &lt;code&gt;required&lt;/code&gt;, &lt;code&gt;additionalProperties: false&lt;/code&gt; 규칙을 지킵니다.&lt;/li&gt;
&lt;li&gt;선택적 필드는 &lt;code&gt;null&lt;/code&gt; 허용으로 모델링합니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;refusal&lt;/code&gt;과 잘림 응답을 분기 처리합니다.&lt;/li&gt;
&lt;li&gt;값 수준 비즈니스 규칙은 별도로 검증합니다.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;에서 요청, 응답 검증, 목업을 구성하고 CI에서 실행합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;구조화된 출력은 모델 응답의 형태를 고정합니다. 테스트는 그 형태가 계속 유지되는지 증명합니다.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>오픈AI 배치 API 사용법</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Thu, 25 Jun 2026 10:07:59 +0000</pubDate>
      <link>https://dev.to/rihpig/opeunai-baeci-api-sayongbeob-549m</link>
      <guid>https://dev.to/rihpig/opeunai-baeci-api-sayongbeob-549m</guid>
      <description>&lt;p&gt;이 가이드에서는 OpenAI &lt;a href="https://developers.openai.com/api/docs/guides/batch" rel="noopener noreferrer"&gt;배치 API&lt;/a&gt;를 사용해 수천 개의 모델 요청을 하나의 비동기 작업으로 실행하고, 결과를 50% 할인된 토큰 비용으로 가져오는 구현 흐름을 다룹니다. JSONL 파일을 만들고, 배치를 제출하고, 상태를 폴링하고, 결과를 다운로드한 뒤, 프로덕션에 연결하기 전에 &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;에서 각 단계를 검증합니다. 작업이 사용자와 실시간으로 상호작용해야 한다면 배치 대신 동기 API를 사용하고, &lt;a href="https://apidog.com/kr/blog/how-to-test-chatgpt-api-with-apidog?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog로 ChatGPT API를 테스트&lt;/a&gt;하는 흐름이 더 적합합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;오늘 Apidog를 사용해 보세요&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  배치 API란 무엇이며 언제 사용해야 할까요?
&lt;/h2&gt;

&lt;p&gt;배치 API는 지연을 허용할 수 있는 대량 모델 호출을 처리하기 위한 비동기 엔드포인트입니다.&lt;/p&gt;

&lt;p&gt;일반적인 동기 호출은 프롬프트마다 HTTP 요청을 보냅니다. 배치 API는 여러 요청을 하나의 JSONL 파일로 묶어 업로드한 뒤, 하나의 배치 작업으로 제출합니다. 이후 작업 상태를 폴링하고, 완료되면 출력 파일을 다운로드합니다.&lt;/p&gt;

&lt;p&gt;배치 API의 핵심 이점은 두 가지입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;동기 API 대비 입력 및 출력 토큰 모두 50% 할인&lt;/li&gt;
&lt;li&gt;실시간 트래픽과 분리된 별도 속도 제한 풀 사용&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;단점은 지연 시간입니다. OpenAI는 배치가 24시간 이내에 완료된다고 보장합니다. 많은 작업은 더 빨리 끝날 수 있지만, 시스템은 24시간 상한을 기준으로 설계해야 합니다.&lt;/p&gt;

&lt;p&gt;배치 API는 다음과 같은 오프라인 대량 작업에 적합합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;과거 데이터 백로그 분류 또는 태그 지정&lt;/li&gt;
&lt;li&gt;전체 코퍼스에 대한 임베딩 생성&lt;/li&gt;
&lt;li&gt;제품 설명, 요약, 번역 등 대량 콘텐츠 생성&lt;/li&gt;
&lt;li&gt;데이터셋 기반 평가 스위트 또는 모델 비교 실행&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;반대로 사용자가 응답을 기다리는 기능에는 사용하지 마세요. 채팅 UI, 자동 완성, 라이브 에이전트는 동기식 엔드포인트가 필요합니다. 여러 모델 또는 에이전트 구성을 한 번에 생성하는 경우에는 배치 처리가 잘 맞습니다. 관련 예시는 &lt;a href="https://apidog.com/kr/blog/llm-configuration-generation-agents?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;배치 처리로 100개 이상의 에이전트 구성 생성&lt;/a&gt; 가이드를 참고하세요.&lt;/p&gt;

&lt;h2&gt;
  
  
  전체 흐름
&lt;/h2&gt;

&lt;p&gt;배치 API 구현은 크게 네 단계입니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;엔드포인트&lt;/th&gt;
&lt;th&gt;수행 작업&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. 업로드&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/files&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;purpose: "batch"&lt;/code&gt;와 함께 &lt;code&gt;.jsonl&lt;/code&gt; 파일을 업로드하고 파일 ID를 받습니다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. 생성&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/batches&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;파일 ID, 대상 엔드포인트, 완료 기간을 제출합니다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. 폴링&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /v1/batches/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;status&lt;/code&gt;가 &lt;code&gt;completed&lt;/code&gt;가 될 때까지 확인합니다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. 검색&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /v1/files/{id}/content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;output_file_id&lt;/code&gt;로 결과 파일을 다운로드합니다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;준비물은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI API 키&lt;/li&gt;
&lt;li&gt;요청을 담은 JSONL 파일&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;curl&lt;/code&gt;, Apidog 등 HTTP 요청을 실행하고 응답을 확인할 도구&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;환경 변수로 API 키를 설정합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_api_key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1단계: JSONL 요청 파일 만들기
&lt;/h2&gt;

&lt;p&gt;입력 파일은 JSONL 형식입니다. 각 줄은 하나의 독립적인 요청입니다.&lt;/p&gt;

&lt;p&gt;각 줄에는 다음 필드가 필요합니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;custom_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;결과와 원본 요청을 매칭하기 위한 고유 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;method&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;일반적으로 &lt;code&gt;POST&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;실행할 대상 엔드포인트. 예: &lt;code&gt;/v1/chat/completions&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;body&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;실제 API 요청 본문&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;예를 들어 감정 분류 작업을 배치로 실행하려면 &lt;code&gt;requests.jsonl&lt;/code&gt; 파일을 다음처럼 만들 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"custom_id": "req-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4.1-mini", "messages": [{"role": "user", "content": "Classify the sentiment of: 'shipping was slow but the product is great'"}]}}
{"custom_id": "req-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4.1-mini", "messages": [{"role": "user", "content": "Classify the sentiment of: 'returned it the same day'"}]}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;중요한 점은 &lt;code&gt;custom_id&lt;/code&gt;입니다. 출력 결과는 입력 순서대로 반환된다는 보장이 없습니다. 따라서 결과를 원본 요청과 매칭할 때 줄 번호가 아니라 &lt;code&gt;custom_id&lt;/code&gt;를 기준으로 처리해야 합니다.&lt;/p&gt;

&lt;p&gt;제약 조건도 확인하세요.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;단일 배치: 최대 50,000개 요청&lt;/li&gt;
&lt;li&gt;파일 크기: 최대 200MB&lt;/li&gt;
&lt;li&gt;파일 내 &lt;code&gt;custom_id&lt;/code&gt;: 고유해야 함&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2단계: JSONL 파일 업로드
&lt;/h2&gt;

&lt;p&gt;파일 API에 &lt;code&gt;purpose="batch"&lt;/code&gt;로 JSONL 파일을 업로드합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.openai.com/v1/files &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="nv"&gt;purpose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"batch"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@requests.jsonl"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;응답에서 &lt;code&gt;id&lt;/code&gt;를 확인합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file-abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"purpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"batch"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 &lt;code&gt;id&lt;/code&gt;가 다음 단계에서 사용할 &lt;code&gt;input_file_id&lt;/code&gt;입니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  3단계: 배치 생성
&lt;/h2&gt;

&lt;p&gt;업로드한 파일 ID로 배치 작업을 생성합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.openai.com/v1/batches &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "input_file_id": "file-abc123",
    "endpoint": "/v1/chat/completions",
    "completion_window": "24h",
    "metadata": {
      "job": "sentiment-backfill"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;endpoint&lt;/code&gt; 값은 JSONL 각 줄의 &lt;code&gt;url&lt;/code&gt;과 일치해야 합니다.&lt;/p&gt;

&lt;p&gt;예를 들어 JSONL에서 &lt;code&gt;url&lt;/code&gt;이 &lt;code&gt;/v1/chat/completions&lt;/code&gt;라면 배치 생성 요청의 &lt;code&gt;endpoint&lt;/code&gt;도 &lt;code&gt;/v1/chat/completions&lt;/code&gt;여야 합니다.&lt;/p&gt;

&lt;p&gt;지원되는 대상에는 다음이 포함됩니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v1/responses&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v1/embeddings&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v1/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v1/moderations&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;metadata&lt;/code&gt;는 선택 사항입니다. 최대 16개의 키-값 쌍을 넣을 수 있으며, 작업 추적이나 비용 분류에 유용합니다.&lt;/p&gt;

&lt;p&gt;배치 생성 응답 예시는 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"batch_abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"batch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/v1/chat/completions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_file_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file-abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completion_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"24h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"validating"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_file_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error_file_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request_counts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1733452800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"job"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sentiment-backfill"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;여기서 저장해야 할 값은 &lt;code&gt;id&lt;/code&gt;입니다. 이후 상태 조회에 사용합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;BATCH_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"batch_abc123"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4단계: 배치 상태 폴링
&lt;/h2&gt;

&lt;p&gt;배치는 처음에 &lt;code&gt;validating&lt;/code&gt; 상태로 시작합니다. 이후 처리 상태에 따라 값이 변경됩니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.openai.com/v1/batches/&lt;span class="nv"&gt;$BATCH_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;주요 상태는 다음과 같습니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;상태&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;validating&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;실행 전 입력 파일을 검증 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;in_progress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;요청 처리 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;finalizing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;실행이 끝났고 출력 파일 준비 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;completed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;완료. 결과 다운로드 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;유효성 검사 실패. 요청은 실행되지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expired&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;24시간 내 모든 요청이 완료되지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;cancelling&lt;/code&gt; / &lt;code&gt;cancelled&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;취소 요청 중 또는 취소 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;in_progress&lt;/code&gt; 상태에서는 &lt;code&gt;request_counts&lt;/code&gt;로 진행률을 확인할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request_counts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;폴링은 너무 자주 하지 마세요. 웹훅을 기다리는 방식이 아니므로, 몇 초 단위가 아니라 몇 분 간격으로 확인하는 패턴이 적합합니다.&lt;/p&gt;

&lt;p&gt;간단한 폴링 스크립트 예시는 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.openai.com/v1/batches/&lt;span class="nv"&gt;$BATCH_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="s1"&gt;'{id, status, request_counts, output_file_id, error_file_id}'&lt;/span&gt;

  &lt;span class="nb"&gt;sleep &lt;/span&gt;120
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;잘못 제출한 배치는 취소할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.openai.com/v1/batches/&lt;span class="nv"&gt;$BATCH_ID&lt;/span&gt;/cancel &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5단계: 결과 파일 다운로드
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;status&lt;/code&gt;가 &lt;code&gt;completed&lt;/code&gt;가 되면 배치 객체에 &lt;code&gt;output_file_id&lt;/code&gt;가 포함됩니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_file_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file-output456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error_file_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file-error789"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;출력 파일을 다운로드합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.openai.com/v1/files/file-output456/content &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; results.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;결과도 JSONL 형식입니다. 각 줄에는 원래 요청의 &lt;code&gt;custom_id&lt;/code&gt;와 응답 정보가 들어 있습니다.&lt;/p&gt;

&lt;p&gt;출력 예시는 다음과 비슷합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"custom_id":"req-1","response":{"status_code":200,"request_id":"request-1","body":{"choices":[{"message":{"role":"assistant","content":"Mixed positive"}}]}}}
{"custom_id":"req-2","response":{"status_code":200,"request_id":"request-2","body":{"choices":[{"message":{"role":"assistant","content":"Negative"}}]}}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;오류가 있는 요청은 &lt;code&gt;error_file_id&lt;/code&gt;가 가리키는 파일에서 확인합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.openai.com/v1/files/file-error789/content &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; errors.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;결과 처리 시에는 반드시 &lt;code&gt;custom_id&lt;/code&gt;를 기준으로 원본 데이터와 조인하세요. 출력 줄 순서를 신뢰하면 안 됩니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  비용과 처리 시간 설계
&lt;/h2&gt;

&lt;p&gt;배치 API의 비용 모델은 단순합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;입력 토큰 50% 할인&lt;/li&gt;
&lt;li&gt;출력 토큰 50% 할인&lt;/li&gt;
&lt;li&gt;최대 24시간 처리 시간 허용&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;따라서 다음 작업에는 적합합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;야간 배치&lt;/li&gt;
&lt;li&gt;일회성 백필&lt;/li&gt;
&lt;li&gt;비동기 리포트 생성&lt;/li&gt;
&lt;li&gt;대량 평가 작업&lt;/li&gt;
&lt;li&gt;대량 임베딩 생성&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;반대로 제품의 핵심 실시간 경로에는 적합하지 않습니다.&lt;/p&gt;

&lt;p&gt;운영 시 고려할 점은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;할인은 지원되는 모델의 입력 및 출력 토큰 모두에 적용됩니다.&lt;/li&gt;
&lt;li&gt;24시간은 목표 시간이 아니라 상한선입니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;expired&lt;/code&gt;가 되면 완료된 요청은 반환 및 청구되고, 완료되지 않은 요청은 반환되지 않습니다.&lt;/li&gt;
&lt;li&gt;배치 작업은 별도 대기열 토큰 제한을 사용하므로 실시간 API 속도 제한을 직접 잠식하지 않습니다.&lt;/li&gt;
&lt;li&gt;대량 작업은 반값이어도 총액이 커질 수 있으므로 &lt;code&gt;metadata&lt;/code&gt;로 작업별 비용을 추적하세요.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;동기식 API의 속도 제한을 함께 다뤄야 한다면 &lt;a href="https://apidog.com/kr/blog/gpt-api-rate-limits?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT API 속도 제한 및 테스트 방법&lt;/a&gt;을 참고하세요. 작업별 비용 할당이 필요하다면 &lt;a href="https://apidog.com/kr/blog/track-openai-api-spend-per-feature?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenAI 지출에 대한 비용 할당 플레이북&lt;/a&gt;을 확인할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apidog에서 배치 API 테스트하기
&lt;/h2&gt;

&lt;p&gt;배치 API는 일반적인 단일 채팅 호출보다 실패 지점이 많습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSONL 형식 오류&lt;/li&gt;
&lt;li&gt;누락된 &lt;code&gt;custom_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;잘못된 &lt;code&gt;url&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;배치 &lt;code&gt;endpoint&lt;/code&gt;와 JSONL &lt;code&gt;url&lt;/code&gt; 불일치&lt;/li&gt;
&lt;li&gt;파일 업로드 실패&lt;/li&gt;
&lt;li&gt;폴링 로직 오류&lt;/li&gt;
&lt;li&gt;결과 파일 파싱 오류&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;자동화하기 전에 &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;에서 전체 수명 주기를 수동으로 검증하면 불필요한 24시간 왕복을 줄일 수 있습니다. Apidog는 OpenAI SDK가 아니라 API 요청을 구성, 실행, 확인, 모의할 수 있는 API 플랫폼입니다.&lt;/p&gt;

&lt;p&gt;권장 테스트 흐름은 다음과 같습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. JSONL 형식 검증
&lt;/h3&gt;

&lt;p&gt;업로드 전에 각 줄이 다음 필드를 포함하는지 확인합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"custom_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"req-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/v1/chat/completions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;특히 다음을 확인하세요.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;custom_id&lt;/code&gt;가 비어 있지 않은지&lt;/li&gt;
&lt;li&gt;파일 내 &lt;code&gt;custom_id&lt;/code&gt;가 중복되지 않는지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;method&lt;/code&gt;가 &lt;code&gt;POST&lt;/code&gt;인지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;url&lt;/code&gt;이 배치 생성 시 사용할 &lt;code&gt;endpoint&lt;/code&gt;와 같은지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;body.model&lt;/code&gt;이 있는지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;messages&lt;/code&gt; 또는 대상 엔드포인트에 필요한 필드가 있는지&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. 파일 업로드 요청 만들기
&lt;/h3&gt;

&lt;p&gt;Apidog에서 &lt;code&gt;POST /v1/files&lt;/code&gt; 요청을 만들고 multipart form-data로 설정합니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;키&lt;/th&gt;
&lt;th&gt;값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;purpose&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;batch&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;requests.jsonl&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;응답의 &lt;code&gt;id&lt;/code&gt;를 환경 변수로 저장합니다.&lt;/p&gt;

&lt;p&gt;예:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input_file_id = file-abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. 배치 생성 요청 실행
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;POST /v1/batches&lt;/code&gt; 요청을 만들고 다음 본문을 보냅니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_file_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{input_file_id}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/v1/chat/completions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completion_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"24h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"job"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sentiment-backfill"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;응답에서 다음 필드를 확인합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt;가 &lt;code&gt;validating&lt;/code&gt;인지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;endpoint&lt;/code&gt;가 기대한 값인지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;input_file_id&lt;/code&gt;가 업로드한 파일 ID인지&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt;가 반환되었는지&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;반환된 배치 ID도 환경 변수로 저장합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;batch_id = batch_abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. 상태 폴링 요청 구성
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;GET /v1/batches/{{batch_id}}&lt;/code&gt; 요청을 만들고 주기적으로 실행합니다.&lt;/p&gt;

&lt;p&gt;확인할 필드는 다음입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;request_counts.total&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;request_counts.completed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;request_counts.failed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;output_file_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;error_file_id&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;status&lt;/code&gt;가 &lt;code&gt;completed&lt;/code&gt;가 되면 &lt;code&gt;output_file_id&lt;/code&gt;를 저장합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 결과 다운로드 요청 만들기
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;GET /v1/files/{{output_file_id}}/content&lt;/code&gt; 요청을 실행해 결과 JSONL을 다운로드합니다.&lt;/p&gt;

&lt;p&gt;오류 파일이 있으면 &lt;code&gt;error_file_id&lt;/code&gt;도 동일하게 다운로드합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. 취소 및 실패 경로 테스트
&lt;/h3&gt;

&lt;p&gt;정상 경로만 테스트하지 마세요. 다음 케이스도 미리 확인하는 것이 좋습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;일부러 잘못된 JSONL 파일을 업로드해 &lt;code&gt;failed&lt;/code&gt; 상태 확인&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;endpoint&lt;/code&gt;와 JSONL &lt;code&gt;url&lt;/code&gt;을 다르게 설정해 오류 처리 확인&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /v1/batches/{id}/cancel&lt;/code&gt;로 취소 요청 확인&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;error_file_id&lt;/code&gt;가 있는 경우 파싱 로직 확인&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;출력 파일은 나중에 도착하므로, 개발 중에는 &lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;모의 API&lt;/a&gt;를 만들어 샘플 배치 객체와 결과 파일을 반환하도록 구성할 수 있습니다. 이렇게 하면 실제 24시간 작업을 기다리거나 토큰을 소비하지 않고 결과 다운로드 및 파싱 로직을 구현할 수 있습니다.&lt;/p&gt;

&lt;p&gt;팀이 사양 우선 방식으로 작업한다면 &lt;a href="https://apidog.com/kr/blog/api-test-collections-generation-openapi-specs?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenAPI 사양에서 직접 테스트 컬렉션을 생성&lt;/a&gt;하고 CI에서 배치 엔드포인트를 회귀 테스트 범위에 포함할 수도 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문
&lt;/h2&gt;

&lt;h3&gt;
  
  
  실제로 배치는 얼마나 걸리나요?
&lt;/h3&gt;

&lt;p&gt;OpenAI는 배치가 24시간 이내에 완료된다고 보장합니다. 실제로는 더 빨리 끝나는 작업도 많지만, 시스템은 24시간 상한을 기준으로 설계해야 합니다.&lt;/p&gt;

&lt;p&gt;작업이 기간 내 완료되지 않으면 배치는 &lt;code&gt;expired&lt;/code&gt; 상태가 됩니다. 이 경우 완료된 요청만 반환되고 청구됩니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  할인은 어느 정도인가요?
&lt;/h3&gt;

&lt;p&gt;배치 API는 동기식 엔드포인트 대비 입력 및 출력 토큰 모두 50% 고정 할인을 제공합니다.&lt;/p&gt;

&lt;p&gt;기능이나 작업 단위로 비용을 나누어 추적하려면 &lt;code&gt;metadata&lt;/code&gt;를 사용해 배치 작업을 태깅하세요. 비용 분할 방식은 &lt;a href="https://apidog.com/kr/blog/track-openai-api-spend-per-feature?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;비용 할당 플레이북&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  배치에서 어떤 엔드포인트를 실행할 수 있나요?
&lt;/h3&gt;

&lt;p&gt;JSONL의 &lt;code&gt;url&lt;/code&gt;과 배치 생성 요청의 &lt;code&gt;endpoint&lt;/code&gt;가 모두 같은 대상을 가리켜야 합니다.&lt;/p&gt;

&lt;p&gt;지원되는 대상에는 다음이 포함됩니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v1/responses&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v1/embeddings&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v1/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v1/moderations&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;이미지 및 비디오 관련 엔드포인트&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;지원 목록은 시간이 지나면서 바뀔 수 있으므로 OpenAI의 최신 문서를 확인하세요.&lt;/p&gt;

&lt;h3&gt;
  
  
  결과 순서가 입력 순서와 다른 이유는 무엇인가요?
&lt;/h3&gt;

&lt;p&gt;의도된 동작입니다. 출력 JSONL은 입력 줄 순서를 보장하지 않습니다.&lt;/p&gt;

&lt;p&gt;그래서 모든 요청에 고유한 &lt;code&gt;custom_id&lt;/code&gt;가 필요합니다. 결과 처리 로직은 반드시 &lt;code&gt;custom_id&lt;/code&gt;를 기준으로 원본 요청과 응답을 매칭해야 합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  결론
&lt;/h2&gt;

&lt;p&gt;OpenAI 배치 API의 구현 흐름은 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;요청을 JSONL 파일로 작성&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /v1/files&lt;/code&gt;로 업로드&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /v1/batches&lt;/code&gt;로 배치 생성&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/batches/{id}&lt;/code&gt;로 상태 폴링&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;output_file_id&lt;/code&gt;로 결과 다운로드&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;custom_id&lt;/code&gt; 기준으로 결과 매칭&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;배치 API는 실시간 응답이 필요 없는 대량 작업에서 토큰 비용을 절반으로 줄일 수 있는 실용적인 방법입니다. 다만 JSONL 형식과 폴링, 오류 파일 처리까지 포함해 전체 수명 주기를 검증해야 안정적으로 운영할 수 있습니다.&lt;/p&gt;

&lt;p&gt;자동화 전에 &lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 다운로드&lt;/a&gt;해 업로드, 생성, 폴링, 취소, 결과 다운로드 흐름을 먼저 실행해 보세요. 잘못된 JSONL 한 줄 때문에 24시간을 낭비하는 상황을 줄일 수 있습니다.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>구글 ADK (에이전트 개발 키트) 이해 및 실전 가이드</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Thu, 25 Jun 2026 08:08:43 +0000</pubDate>
      <link>https://dev.to/rihpig/gugeul-adk-eijeonteu-gaebal-kiteu-ihae-mic-siljeon-gaideu-2c9g</link>
      <guid>https://dev.to/rihpig/gugeul-adk-eijeonteu-gaebal-kiteu-ihae-mic-siljeon-gaideu-2c9g</guid>
      <description>&lt;p&gt;Google ADK는 AI 에이전트를 구축, 평가 및 배포하기 위한 오픈 소스 프레임워크입니다. Agentspace와 같은 Google 제품에서 실제 에이전트를 지원하며, &lt;a href="https://apidog.com/kr/blog/how-to-use-openai-agents-sdk?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenAI Agents SDK&lt;/a&gt;와 유사하게 에이전트 정의, 도구 연결, 실행, 평가까지 다룹니다. 이 글에서는 ADK의 핵심 구성 요소를 빠르게 정리하고, &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;로 에이전트가 호출하는 API를 테스트하는 방법까지 구현 관점에서 살펴봅니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;오늘 Apidog를 사용해 보세요&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Google ADK란 무엇인가
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://adk.dev/" rel="noopener noreferrer"&gt;ADK&lt;/a&gt;는 Agent Development Kit의 약자입니다. Google은 2025년 4월 Google Cloud Next에서 ADK를 공개했으며, 목적은 에이전트의 전체 수명 주기를 코드로 다루는 것입니다.&lt;/p&gt;

&lt;p&gt;ADK로 할 수 있는 일은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;에이전트 정의&lt;/li&gt;
&lt;li&gt;도구 연결&lt;/li&gt;
&lt;li&gt;여러 에이전트 구성&lt;/li&gt;
&lt;li&gt;에이전트 동작 평가&lt;/li&gt;
&lt;li&gt;프로덕션 배포&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fagent-cli.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fagent-cli.gif" alt="" width="600" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ADK는 Python으로 시작했고, 이후 Java가 추가되었으며 Go 및 TypeScript 지원도 뒤따랐습니다. Google이 Agentspace 및 Customer Engagement Suite에서 사용하는 에이전트 기반과 연결되어 있으므로, 단순한 실험용 SDK가 아니라 프로덕션 워크로드를 염두에 둔 프레임워크입니다.&lt;/p&gt;

&lt;p&gt;ADK는 모델에 구애받지 않지만 Google 생태계에 최적화되어 있습니다. Gemini 및 Vertex AI Model Garden의 모델과 잘 맞고, LiteLLM을 통해 Anthropic, Meta, Mistral 등 다른 제공업체의 모델도 연결할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gemini 및 Vertex AI 생태계에서 ADK의 위치
&lt;/h2&gt;

&lt;p&gt;ADK를 이해하려면 세 레이어로 나누어 보는 것이 좋습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;모델&lt;/strong&gt;: Gemini, Vertex AI Model Garden 모델, 또는 LiteLLM을 통한 외부 모델이 추론을 수행합니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;프레임워크&lt;/strong&gt;: ADK가 에이전트 정의, 도구 연결, 다중 에이전트 오케스트레이션을 담당합니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;런타임&lt;/strong&gt;: &lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/overview" rel="noopener noreferrer"&gt;Vertex AI Agent Engine&lt;/a&gt;은 프로덕션에서 에이전트를 실행하기 위한 관리형 런타임입니다. 필요하면 Cloud Run 또는 다른 컨테이너 런타임에도 배포할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-465.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-465.png" alt="" width="800" height="767"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;즉, ADK는 개발자가 직접 다루는 코드 레이어입니다. Gemini는 지능을 제공하고, Vertex AI Agent Engine은 관리형 실행 환경을 제공합니다. 세 가지를 함께 사용할 수도 있고, ADK를 로컬에서 실행한 뒤 다른 컨테이너 플랫폼에 배포할 수도 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  핵심 개념
&lt;/h2&gt;

&lt;p&gt;ADK로 구현할 때 가장 자주 사용하는 구성 요소는 다음입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 에이전트
&lt;/h3&gt;

&lt;p&gt;ADK의 기본 단위는 LLM 기반 에이전트입니다. Python에서는 &lt;code&gt;google.adk.agents&lt;/code&gt;에서 가져옵니다. 클래스 이름은 &lt;code&gt;LlmAgent&lt;/code&gt;이고, &lt;code&gt;Agent&lt;/code&gt;는 이를 위한 편리한 별칭입니다.&lt;/p&gt;

&lt;p&gt;에이전트를 만들 때 주로 지정하는 값은 다음입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt;: 에이전트 이름&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;model&lt;/code&gt;: 사용할 모델&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;instruction&lt;/code&gt;: 에이전트의 역할과 행동 규칙&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tools&lt;/code&gt;: 에이전트가 호출할 수 있는 함수 목록&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sub_agents&lt;/code&gt;: 위임 가능한 하위 에이전트 목록
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_exchange_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return the exchange rate between two currencies.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# 실제 환율 API를 여기에서 호출합니다.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.08&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;currency_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency_exchange_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.0-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You help users convert between currencies. Stick to the facts.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_exchange_rate&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 예제에서 &lt;code&gt;currency_agent&lt;/code&gt;는 사용자 요청을 해석하고, 필요하면 &lt;code&gt;get_exchange_rate()&lt;/code&gt; 함수를 도구로 호출합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 도구
&lt;/h3&gt;

&lt;p&gt;도구는 에이전트가 텍스트 생성 외의 작업을 수행하는 방법입니다. ADK에서는 일반 Python 함수가 도구가 될 수 있습니다.&lt;/p&gt;

&lt;p&gt;모델은 다음 정보를 보고 도구 사용 여부를 판단합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;함수 이름&lt;/li&gt;
&lt;li&gt;타입 힌트&lt;/li&gt;
&lt;li&gt;독스트링&lt;/li&gt;
&lt;li&gt;반환 구조&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;따라서 도구 함수는 명확하게 작성해야 합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Find an order by order_id and return its status, items, and total price.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# 실제 주문 API 호출
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keyboard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mouse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;120.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ADK는 자체 함수 외에도 다음을 지원합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;google_search&lt;/code&gt; 같은 내장 도구&lt;/li&gt;
&lt;li&gt;코드 실행 도구&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apidog.com/kr/blog/mcp-servers-openai-agents?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Model Context Protocol, MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;LangChain 또는 LlamaIndex 같은 외부 라이브러리 래핑&lt;/li&gt;
&lt;li&gt;다른 에이전트를 도구처럼 사용하는 패턴&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;실제 프로젝트에서는 대부분의 도구가 내부 REST API, 외부 SaaS API, LLM 엔드포인트 등을 호출합니다. 따라서 도구가 호출하는 API의 응답 형식을 테스트하고 목업하는 과정이 중요합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 다중 에이전트 시스템
&lt;/h3&gt;

&lt;p&gt;단일 에이전트로도 많은 작업을 처리할 수 있지만, ADK는 여러 에이전트를 조합하는 구조에 강점이 있습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 여행 계획 에이전트를 만든다면 다음처럼 역할을 나눌 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;항공권 검색 에이전트&lt;/li&gt;
&lt;li&gt;호텔 검색 에이전트&lt;/li&gt;
&lt;li&gt;일정 생성 에이전트&lt;/li&gt;
&lt;li&gt;최종 응답을 조합하는 코디네이터 에이전트&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-464.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-464.png" alt="" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ADK는 결정적 제어를 위한 워크플로 에이전트도 제공합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SequentialAgent&lt;/code&gt;: 하위 에이전트를 순서대로 실행&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ParallelAgent&lt;/code&gt;: 하위 에이전트를 동시에 실행&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LoopAgent&lt;/code&gt;: 조건이 충족될 때까지 반복 실행&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LLM 기반 라우팅과 이러한 워크플로 에이전트를 함께 사용하면, 작업을 여러 전문 에이전트에 분산하고 결과를 병합하는 구조를 만들 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. 러너
&lt;/h3&gt;

&lt;p&gt;프로덕션 코드에서는 에이전트를 직접 호출하기보다 &lt;code&gt;Runner&lt;/code&gt;를 사용합니다. &lt;code&gt;Runner&lt;/code&gt;는 ADK의 실행 엔진입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Runner&lt;/code&gt;가 담당하는 작업은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;세션 관리&lt;/li&gt;
&lt;li&gt;이벤트 흐름 처리&lt;/li&gt;
&lt;li&gt;상태 업데이트&lt;/li&gt;
&lt;li&gt;모델 호출&lt;/li&gt;
&lt;li&gt;도구 호출 조정&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;개발 중에는 CLI를 사용하면 빠르게 확인할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adk run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;adk run&lt;/code&gt;은 대화형 터미널 세션을 시작합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adk web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;adk web&lt;/code&gt;은 브라우저에서 에이전트와 대화하고 각 단계를 검사할 수 있는 로컬 UI를 엽니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 평가 및 배포
&lt;/h3&gt;

&lt;p&gt;ADK에는 평가 기능이 포함되어 있습니다. 단순히 결과를 눈으로 확인하는 대신, 예상 응답이나 예상 실행 경로에 대해 에이전트 동작을 검증할 수 있습니다.&lt;/p&gt;

&lt;p&gt;이 기능은 다음 상황에서 중요합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;프롬프트를 수정할 때&lt;/li&gt;
&lt;li&gt;도구 응답 형식이 바뀔 때&lt;/li&gt;
&lt;li&gt;모델을 교체할 때&lt;/li&gt;
&lt;li&gt;하위 에이전트 구성을 변경할 때&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;배포 옵션은 크게 두 가지입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;관리형 배포&lt;/strong&gt;: Vertex AI Agent Engine 사용&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;휴대 가능한 배포&lt;/strong&gt;: 컨테이너로 패키징 후 Cloud Run 또는 다른 컨테이너 플랫폼에 배포&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  고수준 예시: 여행 계획 다중 에이전트
&lt;/h2&gt;

&lt;p&gt;다음은 두 개의 전문 에이전트와 하나의 코디네이터 에이전트를 구성하는 예시입니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;

&lt;span class="n"&gt;flights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flight_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.0-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Find flight options for the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s route and dates.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;search_flights&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;# 항공권 API를 래핑하는 함수
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;hotels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hotel_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.0-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Find hotel options near the destination.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;search_hotels&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;    &lt;span class="c1"&gt;# 호텔 API를 래핑하는 함수
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;trip_planner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trip_planner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.0-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Plan a trip. Delegate flight and hotel lookups to your sub-agents.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sub_agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;flights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hotels&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 구조에서 &lt;code&gt;trip_planner&lt;/code&gt;는 사용자 요청을 해석한 뒤, 항공권이 필요하면 &lt;code&gt;flight_agent&lt;/code&gt;에, 숙소가 필요하면 &lt;code&gt;hotel_agent&lt;/code&gt;에 위임합니다.&lt;/p&gt;

&lt;p&gt;각 하위 에이전트는 도구 함수를 통해 실제 API를 호출합니다. 개발 중에는 &lt;code&gt;adk web&lt;/code&gt;으로 실행 흐름을 확인하고, 프로덕션에서는 &lt;code&gt;Runner&lt;/code&gt; 또는 관리형 런타임을 통해 실행할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  ADK 대 OpenAI Agents SDK
&lt;/h2&gt;

&lt;p&gt;둘 다 도구 호출, 핸드오프, 추적 기능을 갖춘 코드 우선 에이전트 프레임워크입니다. 차이는 주로 생태계에 있습니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Google ADK&lt;/th&gt;
&lt;th&gt;OpenAI Agents SDK&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;기본 모델&lt;/td&gt;
&lt;td&gt;Gemini, Vertex AI&lt;/td&gt;
&lt;td&gt;OpenAI 모델&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기타 모델&lt;/td&gt;
&lt;td&gt;Vertex AI Model Garden, LiteLLM&lt;/td&gt;
&lt;td&gt;LiteLLM 및 기타&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;언어&lt;/td&gt;
&lt;td&gt;Python, Java, Go, TypeScript&lt;/td&gt;
&lt;td&gt;Python, JavaScript/TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;다중 에이전트&lt;/td&gt;
&lt;td&gt;서브 에이전트 + 순차, 병렬, 루프 워크플로 에이전트&lt;/td&gt;
&lt;td&gt;도구로서의 에이전트 및 핸드오프&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;관리형 런타임&lt;/td&gt;
&lt;td&gt;Vertex AI Agent Engine&lt;/td&gt;
&lt;td&gt;직접 구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도구 프로토콜&lt;/td&gt;
&lt;td&gt;MCP, 내장 도구, 함수 도구&lt;/td&gt;
&lt;td&gt;MCP, 함수 도구&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;이미 Google Cloud와 Vertex AI를 사용하고 있다면 ADK가 자연스럽습니다. OpenAI 모델과 도구 체인을 중심으로 구축한다면 &lt;a href="https://apidog.com/kr/blog/how-to-use-openai-agents-sdk?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;OpenAI Agents SDK&lt;/a&gt;가 더 적합할 수 있습니다. 둘 다 MCP를 지원하므로 동일한 도구 서버를 공유하는 구조도 가능합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  ADK를 사용해야 하는 경우
&lt;/h2&gt;

&lt;p&gt;다음 상황이라면 ADK를 고려할 만합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Cloud 기반으로 구축하고 있다.&lt;/li&gt;
&lt;li&gt;Gemini와 Vertex AI Agent Engine을 함께 사용하고 싶다.&lt;/li&gt;
&lt;li&gt;순차, 병렬, 루프 기반의 명시적 다중 에이전트 제어가 필요하다.&lt;/li&gt;
&lt;li&gt;평가 기능을 프레임워크 안에서 다루고 싶다.&lt;/li&gt;
&lt;li&gt;모델 교체 가능성을 열어두고 싶다.&lt;/li&gt;
&lt;li&gt;Vertex AI Model Garden 또는 LiteLLM을 통해 여러 모델을 연결해야 한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;반대로 다음 상황이라면 ADK가 과할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;단일 프롬프트와 한두 개의 함수 호출로 충분하다.&lt;/li&gt;
&lt;li&gt;다른 모델 생태계에 이미 강하게 묶여 있다.&lt;/li&gt;
&lt;li&gt;에이전트 오케스트레이션보다 단순 API 호출 자동화가 목적이다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;에이전트 프레임워크는 구조를 제공합니다. 하지만 작업이 작을 때는 그 구조 자체가 복잡도가 될 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apidog의 역할: 에이전트가 호출하는 API 테스트 및 목업
&lt;/h2&gt;

&lt;p&gt;ADK는 에이전트를 오케스트레이션합니다. 하지만 에이전트가 의존하는 외부 API 자체를 테스트하지는 않습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-463.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-463.png" alt="" width="799" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;실제 에이전트의 도구는 보통 다음 중 하나를 호출합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM 엔드포인트&lt;/li&gt;
&lt;li&gt;결제 API&lt;/li&gt;
&lt;li&gt;내부 마이크로서비스&lt;/li&gt;
&lt;li&gt;타사 데이터 API&lt;/li&gt;
&lt;li&gt;검색 API&lt;/li&gt;
&lt;li&gt;주문, 사용자, 재고 같은 사내 API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 API 중 하나가 예상과 다른 응답을 반환하면 에이전트는 잘못된 입력을 기반으로 추론합니다. 이 문제는 에이전트 로그만 보고 추적하기 어렵습니다.&lt;/p&gt;

&lt;p&gt;여기서 &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;의 역할은 ADK를 대체하는 것이 아닙니다. Apidog는 에이전트 프레임워크가 아니라, 에이전트 도구가 호출하는 API를 설계, 목업, 테스트하는 레이어입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  ADK 개발 중 Apidog를 사용하는 방법
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. 도구가 호출하는 엔드포인트 목업
&lt;/h4&gt;

&lt;p&gt;실제 API가 아직 준비되지 않았거나, 호출 비용과 속도 제한이 부담된다면 &lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;목업 API&lt;/a&gt;를 먼저 만들 수 있습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 에이전트 도구가 다음 API를 호출한다고 가정합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /exchange-rate?base=USD&amp;amp;target=KRW
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apidog에서 성공 응답을 다음처럼 정의할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"base"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"KRW"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1380.25&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;그리고 에러 케이스도 별도로 만들 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unsupported_currency"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The target currency is not supported."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 하면 실제 API 없이도 에이전트가 정상 응답과 오류 응답을 모두 처리하는지 테스트할 수 있습니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. 도구 응답 형태 검증
&lt;/h4&gt;

&lt;p&gt;에이전트 도구는 반환 필드에 민감합니다. 예를 들어 에이전트가 &lt;code&gt;rate&lt;/code&gt; 필드를 기대하는데 API가 &lt;code&gt;exchangeRate&lt;/code&gt;로 바뀌면, 에이전트는 잘못된 응답을 생성할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/kr/blog/api-assertions?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 단언&lt;/a&gt;을 사용하면 응답 계약을 테스트할 수 있습니다.&lt;/p&gt;

&lt;p&gt;검증해야 할 항목은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;필수 필드 존재 여부&lt;/li&gt;
&lt;li&gt;필드 타입&lt;/li&gt;
&lt;li&gt;상태 코드&lt;/li&gt;
&lt;li&gt;에러 응답 형식&lt;/li&gt;
&lt;li&gt;배열 또는 객체 구조&lt;/li&gt;
&lt;li&gt;인증 실패 응답&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;에이전트 코드에서 문제가 터지기 전에 API 테스트에서 계약 변경을 감지하는 것이 좋습니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. 환경별 키 관리
&lt;/h4&gt;

&lt;p&gt;개발, 스테이징, 프로덕션마다 API 키와 베이스 URL이 다릅니다. Apidog 환경 기능을 사용하면 같은 API 요청을 환경별 변수로 실행할 수 있습니다.&lt;/p&gt;

&lt;p&gt;예:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{base_url}}/exchange-rate?base=USD&amp;amp;target=KRW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;환경별 값:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dev.base_url = https://dev-api.example.com
staging.base_url = https://staging-api.example.com
prod.base_url = https://api.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 패턴을 사용하면 ADK 도구가 호출하는 API를 배포 단계별로 더 안정적으로 검증할 수 있습니다.&lt;/p&gt;

&lt;p&gt;더 자세한 흐름은 &lt;a href="https://apidog.com/kr/blog/ai-agent-apidog-test-harness?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;AI 에이전트의 도구 호출을 테스트하는 방법&lt;/a&gt;을 참고하십시오. &lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 다운로드&lt;/a&gt;하면 단일 엔드포인트 목업부터 바로 시작할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Google ADK는 무료이며 오픈 소스인가요?
&lt;/h3&gt;

&lt;p&gt;네. ADK는 Apache 라이선스 하의 오픈 소스 &lt;a href="https://github.com/google/adk-python" rel="noopener noreferrer"&gt;GitHub 저장소&lt;/a&gt;로 제공됩니다. 프레임워크 자체는 무료로 로컬에서 실행할 수 있습니다.&lt;/p&gt;

&lt;p&gt;다만 호출하는 모델, Vertex AI Agent Engine 같은 관리형 런타임, 클라우드 인프라 비용은 별도로 발생합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  ADK는 Gemini에서만 작동하나요?
&lt;/h3&gt;

&lt;p&gt;아니요. ADK는 Gemini 및 Vertex AI에 최적화되어 있지만 모델에 구애받지 않습니다. Vertex AI Model Garden 및 LiteLLM을 통해 Anthropic, Meta, Mistral 등 다른 제공업체의 모델도 사용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;Gemini는 기본 선택지에 가깝지만 필수는 아닙니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  ADK는 어떤 언어를 지원하나요?
&lt;/h3&gt;

&lt;p&gt;Python이 가장 먼저 출시되었고 가장 완성도가 높습니다. 이후 Java가 추가되었으며 Go 및 TypeScript 지원도 뒤따랐습니다.&lt;/p&gt;

&lt;p&gt;현재 가장 넓은 기능 범위를 원한다면 Python으로 시작하는 것이 안전합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  ADK 에이전트가 의존하는 API는 어떻게 테스트하나요?
&lt;/h3&gt;

&lt;p&gt;에이전트와 API를 분리해서 테스트하는 것이 좋습니다.&lt;/p&gt;

&lt;p&gt;실무에서는 다음 순서가 유용합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;도구가 호출할 API의 요청/응답 계약을 정의합니다.&lt;/li&gt;
&lt;li&gt;Apidog에서 목업 응답을 만듭니다.&lt;/li&gt;
&lt;li&gt;에이전트를 목업 API에 연결합니다.&lt;/li&gt;
&lt;li&gt;정상 응답, 오류 응답, 빈 응답을 테스트합니다.&lt;/li&gt;
&lt;li&gt;API 단언으로 응답 구조가 깨지지 않는지 검증합니다.&lt;/li&gt;
&lt;li&gt;실제 API로 전환하기 전에 환경 변수를 정리합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;LLM 엔드포인트 테스트 패턴은 &lt;a href="https://apidog.com/kr/blog/how-to-test-chatgpt-api-with-apidog?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;ChatGPT API를 테스트하는 방법&lt;/a&gt;에서도 확인할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;Google ADK는 Gemini 및 Vertex AI와 잘 통합되면서도 다른 모델로 확장할 수 있는 에이전트 프레임워크입니다. 하나의 에이전트와 몇 개의 도구로 시작한 뒤, &lt;code&gt;adk web&lt;/code&gt;으로 실행 흐름을 확인하고, 필요할 때 서브 에이전트와 관리형 런타임으로 확장하는 방식이 현실적입니다.&lt;/p&gt;

&lt;p&gt;에이전트가 외부 API에 의존한다면 API 응답 계약을 먼저 안정화해야 합니다. 도구가 호출하는 엔드포인트를 목업하고, 응답 구조를 단언하고, 환경별 설정을 관리하는 레이어가 필요합니다. 이 부분을 &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;로 처리하면 ADK 에이전트의 불안정한 동작을 더 일찍 발견할 수 있습니다.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>LangGraph란? 상태 저장 AI 에이전트 구축 가이드</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Thu, 25 Jun 2026 08:03:05 +0000</pubDate>
      <link>https://dev.to/rihpig/langgraphran-sangtae-jeojang-ai-eijeonteu-gucug-gaideu-3ime</link>
      <guid>https://dev.to/rihpig/langgraphran-sangtae-jeojang-ai-eijeonteu-gucug-gaideu-3ime</guid>
      <description>&lt;p&gt;대부분의 에이전트 코드는 간단한 스크립트로 시작하지만, 재시도·분기·도구 호출이 추가되는 순간 유지보수가 어려워집니다. LLM을 연결하고 도구를 제공한 뒤 워크플로우가 루프를 돌거나, 조건에 따라 분기하거나, 사람의 승인을 기다려야 한다면 직선형 파이프라인만으로는 부족합니다. LangGraph는 에이전트를 공유 상태를 가진 그래프로 모델링해 이러한 제어 흐름을 명시적으로 다루는 프레임워크입니다. 이 글에서는 LangGraph의 핵심 개념, 최소 구현 예시, 영속성 설정, 그리고 &lt;a href="https://apidog.com/kr/blog/how-to-test-ai-agents-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;에이전트가 실제 서비스를 호출할 때 API 테스트를 어디에 적용해야 하는지&lt;/a&gt;를 실무 관점에서 정리합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;오늘 Apidog를 사용해 보세요&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  LangGraph란 무엇인가
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.langchain.com/oss/python/langgraph/overview" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt;는 장기 실행되는 상태 저장 에이전트를 만들기 위한 저수준 오케스트레이션 프레임워크이자 런타임입니다. LangChain 팀인 LangChain Inc에서 만들었지만, LangChain과는 별도의 라이브러리로 사용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;설치는 단순합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; langgraph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-460.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-460.png" alt="" width="799" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LangGraph의 핵심은 에이전트를 &lt;strong&gt;그래프&lt;/strong&gt;로 표현하는 것입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;노드&lt;/strong&gt;: 모델 호출, 도구 실행, 데이터 변환 같은 작업 단위&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;엣지&lt;/strong&gt;: 다음에 실행할 노드를 결정하는 연결&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;상태&lt;/strong&gt;: 모든 노드가 읽고 업데이트하는 공유 객체&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;루프&lt;/strong&gt;: 도구 실행 후 다시 모델로 돌아가는 순환 흐름&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;기존 체인 방식은 보통 &lt;code&gt;1단계 → 2단계 → 3단계 → 종료&lt;/code&gt; 형태입니다. 반면 LangGraph는 현재 상태를 보고 다음 경로를 선택할 수 있습니다. 예를 들어 모델이 도구 호출을 요청하면 도구 노드로 이동하고, 더 이상 호출할 도구가 없으면 종료할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  LangGraph가 해결하는 문제
&lt;/h2&gt;

&lt;p&gt;에이전트 워크플로우는 대부분 선형이 아닙니다. 일반적인 흐름은 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;사용자의 요청을 받습니다.&lt;/li&gt;
&lt;li&gt;모델이 다음 행동을 결정합니다.&lt;/li&gt;
&lt;li&gt;필요한 경우 도구 API를 호출합니다.&lt;/li&gt;
&lt;li&gt;결과를 다시 모델에 전달합니다.&lt;/li&gt;
&lt;li&gt;완료 여부를 판단합니다.&lt;/li&gt;
&lt;li&gt;완료되지 않았다면 다시 반복합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이 흐름을 중첩된 &lt;code&gt;if&lt;/code&gt; 문과 &lt;code&gt;while&lt;/code&gt; 문으로 직접 구현하면 빠르게 복잡해집니다. LangGraph는 다음 기능을 구조적으로 제공합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;정지 조건이 있는 루프&lt;/strong&gt;: 무한 루프를 피하면서 작업 완료 전까지 반복&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;상태 기반 분기&lt;/strong&gt;: 모델 응답, 도구 결과, 사용자 입력에 따라 라우팅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;영속성&lt;/strong&gt;: 충돌, 타임아웃, 재시작 후에도 마지막 체크포인트에서 재개&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-in-the-loop&lt;/strong&gt;: 실행 중간에 사람이 상태를 확인하거나 수정한 뒤 계속 진행&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;스트리밍&lt;/strong&gt;: 토큰과 중간 실행 결과를 실시간으로 UI에 전달&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;즉, LangGraph는 에이전트를 단순 함수 호출 묶음이 아니라 &lt;strong&gt;상태 머신&lt;/strong&gt;으로 다룰 수 있게 해줍니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  핵심 개념: 그래프, 상태, 노드, 엣지
&lt;/h2&gt;

&lt;p&gt;LangGraph를 구현할 때는 네 가지 개념만 먼저 잡으면 됩니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 상태(State)
&lt;/h3&gt;

&lt;p&gt;상태는 전체 실행 동안 공유되는 데이터 구조입니다. 보통 &lt;code&gt;TypedDict&lt;/code&gt; 또는 LangGraph가 제공하는 &lt;code&gt;MessagesState&lt;/code&gt;를 사용합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MessagesState&lt;/code&gt;는 채팅 메시지 목록을 관리하는 기본 상태 스키마입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 노드(Node)
&lt;/h3&gt;

&lt;p&gt;노드는 일반 Python 함수입니다. 현재 상태를 입력으로 받고, 상태에 병합할 업데이트를 반환합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. 엣지(Edge)
&lt;/h3&gt;

&lt;p&gt;엣지는 노드 사이의 이동 경로입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;add_edge()&lt;/code&gt;는 항상 같은 다음 노드로 이동합니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;add_conditional_edges()&lt;/code&gt;는 상태를 보고 다음 노드를 동적으로 결정합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. START와 END
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;START&lt;/code&gt;는 그래프 실행 시작점이고, &lt;code&gt;END&lt;/code&gt;는 종료 지점입니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  최소 LangGraph 예제
&lt;/h2&gt;

&lt;p&gt;아래 예제는 모델이 도구 호출을 요청하면 &lt;code&gt;tools&lt;/code&gt; 노드로 이동하고, 도구 실행 후 다시 모델로 돌아가는 기본 루프입니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langgraph.graph&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MessagesState&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MessagesState&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_continue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MessagesState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;END&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MessagesState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_conditional_edges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;should_continue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 도구 실행 후 다시 모델로 루프
&lt;/span&gt;
&lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;중요한 부분은 이 줄입니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;도구 실행이 끝난 뒤 모델로 돌아가기 때문에 에이전트는 다음을 계속 판단할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;추가 도구 호출이 필요한가?&lt;/li&gt;
&lt;li&gt;사용자에게 답변할 수 있는가?&lt;/li&gt;
&lt;li&gt;오류가 발생했는가?&lt;/li&gt;
&lt;li&gt;종료해야 하는가?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 순환 구조가 LangGraph를 사용하는 핵심 이유입니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  영속성 및 human-in-the-loop 설정
&lt;/h2&gt;

&lt;p&gt;LangGraph에서 체크포인터를 사용하면 각 단계 후 상태 스냅샷을 저장할 수 있습니다. 이후 같은 &lt;code&gt;thread_id&lt;/code&gt;로 호출하면 마지막 상태를 복원합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langgraph.checkpoint.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InMemorySaver&lt;/span&gt;

&lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkpointer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;InMemorySaver&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configurable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thread_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;InMemorySaver&lt;/code&gt;는 개발 환경에서 테스트하기에 적합합니다. 재시작 후에도 상태를 유지해야 한다면 데이터베이스 기반 저장소를 사용해야 합니다. LangGraph는 단일 서버용 SQLite와 다중 인스턴스 환경용 Postgres 기반 저장 옵션을 제공합니다.&lt;/p&gt;

&lt;p&gt;이 구조를 사용하면 human-in-the-loop 흐름도 쉽게 설계할 수 있습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 다음과 같은 승인 게이트를 만들 수 있습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;모델이 외부 API 호출 계획을 생성합니다.&lt;/li&gt;
&lt;li&gt;그래프를 특정 노드에서 일시 중지합니다.&lt;/li&gt;
&lt;li&gt;사람이 요청 본문, 대상 API, 비용 등을 확인합니다.&lt;/li&gt;
&lt;li&gt;승인 또는 수정 후 같은 체크포인트에서 실행을 재개합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;노드 로직에 별도의 저장·복구 코드를 넣지 않아도 런타임이 상태를 관리합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  스트리밍을 UI에 연결하기
&lt;/h2&gt;

&lt;p&gt;LangGraph는 실행 중 모델 토큰과 노드 업데이트를 스트리밍할 수 있습니다. UI에서는 단순 로딩 스피너 대신 다음 정보를 표시할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;현재 실행 중인 노드&lt;/li&gt;
&lt;li&gt;모델의 부분 응답&lt;/li&gt;
&lt;li&gt;도구 호출 요청&lt;/li&gt;
&lt;li&gt;도구 실행 결과&lt;/li&gt;
&lt;li&gt;최종 응답&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;에이전트 디버깅에서는 최종 답변만 보는 것보다 중간 상태를 보는 것이 훨씬 중요합니다. 특히 조건부 엣지가 예상과 다른 경로를 선택하는 경우, 스트리밍 로그가 문제 원인을 찾는 데 도움이 됩니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  LangGraph와 LangChain의 관계
&lt;/h2&gt;

&lt;p&gt;LangChain은 모델 래퍼, 프롬프트 템플릿, 검색기, 문서 로더, 다양한 제공업체 통합을 포함하는 더 넓은 도구 키트입니다. LangGraph는 그중 에이전트 실행 흐름을 담당하는 오케스트레이션 레이어에 가깝습니다.&lt;/p&gt;

&lt;p&gt;LangGraph를 사용하기 위해 반드시 LangChain을 알아야 하는 것은 아닙니다. 노드 내부에서 원하는 모델 클라이언트를 직접 호출할 수 있습니다. 다만 LangChain의 모델 및 도구 추상화가 편리하기 때문에 많은 팀이 두 라이브러리를 함께 사용합니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;LangChain&lt;/th&gt;
&lt;th&gt;LangGraph&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;td&gt;구성 요소 및 통합&lt;/td&gt;
&lt;td&gt;오케스트레이션 및 런타임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;제어 흐름&lt;/td&gt;
&lt;td&gt;선형 체인&lt;/td&gt;
&lt;td&gt;순환 및 분기가 있는 그래프&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;내장 상태&lt;/td&gt;
&lt;td&gt;제한적&lt;/td&gt;
&lt;td&gt;공유, 유형 지정, 내구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;영속성 / 재개&lt;/td&gt;
&lt;td&gt;주요 초점 아님&lt;/td&gt;
&lt;td&gt;체크포인터 + 스레드 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가장 적합한 용도&lt;/td&gt;
&lt;td&gt;모델 호출 및 도구 구성&lt;/td&gt;
&lt;td&gt;상태 저장, 다단계 에이전트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;현재 LangGraph v1.0 라인에서는 사전 빌드된 에이전트 헬퍼의 위치가 변경되고 있으므로, 예전 코드를 복사하기 전에 설치된 버전과 &lt;a href="https://reference.langchain.com/python/langgraph.prebuilt/chat_agent_executor/create_react_agent" rel="noopener noreferrer"&gt;공식 참조 문서&lt;/a&gt;에서 정확한 임포트 경로를 확인해야 합니다.&lt;/p&gt;

&lt;p&gt;더 넓은 관점에서 에이전트 구조를 설계하려면 &lt;a href="https://apidog.com/kr/blog/create-ai-agents?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;사용자 정의 AI 에이전트 구축&lt;/a&gt; 가이드도 참고할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  LangGraph Platform 및 Studio
&lt;/h2&gt;

&lt;p&gt;오픈 소스 라이브러리만으로도 로컬 개발은 가능합니다. 하지만 디버깅과 배포 단계에서는 LangGraph Studio와 LangGraph Platform이 도움이 됩니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  LangGraph Studio
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;LangGraph Studio&lt;/strong&gt;는 &lt;a href="https://www.langchain.com/blog/langgraph-studio-the-first-agent-ide" rel="noopener noreferrer"&gt;시각적 에이전트 IDE&lt;/a&gt;입니다. 그래프 구조를 렌더링하고, 실행 경로와 각 노드의 상태를 확인할 수 있습니다.&lt;/p&gt;

&lt;p&gt;조건부 라우팅이나 루프가 있는 에이전트에서는 로그만으로 흐름을 추적하기 어렵습니다. Studio를 사용하면 실제로 어떤 노드를 거쳤는지 시각적으로 확인할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-461.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-461.png" alt="" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  LangGraph Platform
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;LangGraph Platform&lt;/strong&gt;은 에이전트를 API 엔드포인트로 배포하고, 장기 실행을 위한 영속성 및 호스팅 옵션을 제공하는 관리형 배포 레이어입니다. 자체 호스팅부터 클라우드 관리형 옵션까지 선택할 수 있습니다.&lt;/p&gt;

&lt;p&gt;라이브러리 사용에 필수는 아니지만, 프로덕션에서 에이전트 실행 인프라를 직접 운영하고 싶지 않을 때 유용합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-462.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-462.png" alt="" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  LangGraph를 언제 사용해야 하는가
&lt;/h2&gt;

&lt;p&gt;LangGraph는 모든 LLM 작업에 필요한 도구가 아닙니다. 다음 조건 중 하나라도 해당하면 사용을 검토할 만합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;도구 호출 후 결과를 보고 다시 판단해야 한다.&lt;/li&gt;
&lt;li&gt;모델 출력에 따라 다음 단계가 달라진다.&lt;/li&gt;
&lt;li&gt;실행 시간이 길고, 중간에 실패해도 재개해야 한다.&lt;/li&gt;
&lt;li&gt;실행 도중 사람의 승인이나 수정이 필요하다.&lt;/li&gt;
&lt;li&gt;여러 하위 에이전트 또는 액터가 같은 상태를 공유한다.&lt;/li&gt;
&lt;li&gt;에이전트의 실행 경로를 추적하고 디버깅해야 한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;반대로 다음 작업에는 과할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;단일 프롬프트로 텍스트를 요약하는 작업&lt;/li&gt;
&lt;li&gt;한 번의 모델 호출로 끝나는 분류 작업&lt;/li&gt;
&lt;li&gt;짧고 고정된 선형 파이프라인&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;분기와 루프가 없다면 LangGraph의 구조는 오히려 오버헤드가 될 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  API 테스트 및 목업(Mocking)의 적용 지점
&lt;/h2&gt;

&lt;p&gt;LangGraph는 에이전트의 실행 흐름을 오케스트레이션하지만, 에이전트가 호출하는 API 자체를 검증하지는 않습니다. 실제 에이전트는 보통 다음 API에 의존합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM API&lt;/li&gt;
&lt;li&gt;검색 API&lt;/li&gt;
&lt;li&gt;CRM 또는 내부 백엔드 API&lt;/li&gt;
&lt;li&gt;결제, 주문, 사용자 관리 API&lt;/li&gt;
&lt;li&gt;벡터 데이터베이스 API&lt;/li&gt;
&lt;li&gt;사내 도구 API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 지점에서 &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;를 사용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-460.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-460.png" alt="" width="799" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 실제 API 대신 목업 API로 그래프 로직 테스트
&lt;/h3&gt;

&lt;p&gt;모든 테스트 실행에서 실제 API를 호출하면 비용과 속도 문제가 생깁니다. 특히 LLM API는 토큰 비용과 속도 제한의 영향을 받습니다.&lt;/p&gt;

&lt;p&gt;개발 중에는 에이전트가 의존하는 &lt;a href="https://apidog.com/kr/blog/mock-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API를 목업(mock)&lt;/a&gt;해 그래프 로직을 빠르게 반복할 수 있습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 도구 노드가 다음 API를 호출한다고 가정합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /users/{id}/orders
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;목업 응답을 고정해두면 LangGraph 테스트에서 항상 같은 결과를 받을 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shipped"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 하면 모델 라우팅과 조건부 엣지를 검증할 때 외부 시스템 상태에 흔들리지 않습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 응답 스키마를 단언(assert)해 노드 오류 방지
&lt;/h3&gt;

&lt;p&gt;LangGraph 노드는 보통 API 응답 형태를 가정합니다.&lt;/p&gt;

&lt;p&gt;예를 들어 노드가 &lt;code&gt;status&lt;/code&gt; 필드를 읽는다고 가정합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;route_by_order_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notify_user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_again&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;그런데 백엔드 API가 &lt;code&gt;status&lt;/code&gt;를 &lt;code&gt;orderStatus&lt;/code&gt;로 바꾸면 에이전트는 잘못된 분기로 이동하거나 예외를 낼 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/kr/blog/api-assertions?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 단언(assertions)&lt;/a&gt;을 사용하면 다음 조건을 사전에 검증할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;필수 필드가 존재하는가?&lt;/li&gt;
&lt;li&gt;필드 타입이 예상과 일치하는가?&lt;/li&gt;
&lt;li&gt;상태 코드가 올바른가?&lt;/li&gt;
&lt;li&gt;응답 시간이 기준 이하인가?&lt;/li&gt;
&lt;li&gt;에러 응답 형식이 일관적인가?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;API 계약이 깨졌을 때 그래프 실행 중에 발견하는 것이 아니라, API 테스트 단계에서 먼저 감지하는 것이 안전합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 환경별 키와 엔드포인트 분리
&lt;/h3&gt;

&lt;p&gt;에이전트는 개발, 스테이징, 프로덕션 환경에서 서로 다른 API 키와 엔드포인트를 사용합니다. 이 값을 노드 코드에 직접 넣으면 보안과 유지보수 문제가 생깁니다.&lt;/p&gt;

&lt;p&gt;환경 변수 또는 API 테스트 도구의 환경 관리 기능을 사용해 다음 값을 분리해야 합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BASE_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;API_KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LLM_API_KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MOCK_SERVER_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;STAGING_SERVER_URL&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;완전한 에이전트 중심 워크플로우가 필요하다면 &lt;a href="https://apidog.com/kr/blog/ai-agent-apidog-test-harness?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;AI 에이전트를 위한 Apidog 테스트 하네스&lt;/a&gt;를 참고할 수 있습니다.&lt;/p&gt;

&lt;p&gt;중요한 점은 역할 분리입니다. LangGraph는 에이전트를 오케스트레이션하고, Apidog는 에이전트가 호출하는 API를 테스트하고 목업합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문
&lt;/h2&gt;

&lt;h3&gt;
  
  
  LangGraph가 LangChain을 대체하나요?
&lt;/h3&gt;

&lt;p&gt;아닙니다. LangGraph는 오케스트레이션 런타임이고, LangChain은 모델·도구·검색기·통합을 포함하는 더 넓은 구성 요소 집합입니다. 둘은 같은 팀에서 만든 별도 라이브러리이며, 함께 사용할 수도 있고 LangGraph만 단독으로 사용할 수도 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  LangGraph를 시작하기 위해 LangChain을 알아야 하나요?
&lt;/h3&gt;

&lt;p&gt;아닙니다. &lt;code&gt;StateGraph&lt;/code&gt;를 정의하고, 노드와 엣지를 추가하고, 노드 내부에서 원하는 모델 클라이언트를 호출하면 됩니다. LangChain의 모델 래퍼는 편리하지만 필수는 아닙니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  LangGraph는 호출 간에 어떻게 내용을 기억하나요?
&lt;/h3&gt;

&lt;p&gt;체크포인터를 사용합니다. 체크포인터로 그래프를 컴파일하고 &lt;code&gt;thread_id&lt;/code&gt;를 전달하면 LangGraph가 각 단계 후 상태 스냅샷을 저장합니다. 이후 같은 &lt;code&gt;thread_id&lt;/code&gt;로 호출하면 해당 상태를 복원합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  에이전트가 호출하는 API는 어떻게 테스트하나요?
&lt;/h3&gt;

&lt;p&gt;그래프와 분리해서 테스트하고 목업해야 합니다. 개발 중에는 LLM 및 도구 엔드포인트를 목업해 비용과 지연을 줄이고, 응답 스키마를 단언해 필드 변경으로 인한 노드 오류를 방지해야 합니다. &lt;a href="https://apidog.com/kr/blog/how-to-test-chatgpt-api-with-apidog?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;ChatGPT API 테스트&lt;/a&gt; 가이드는 인증, 스트리밍, 도구 호출처럼 에이전트가 의존하는 주요 부분을 다룹니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  요약
&lt;/h2&gt;

&lt;p&gt;LangGraph는 루프, 분기, 영속성, human-in-the-loop가 필요한 에이전트에 적합한 오케스트레이션 프레임워크입니다. 워크플로우를 공유 상태 기반 그래프로 모델링하고, 체크포인터로 실행 상태를 저장하며, 필요하면 Studio와 Platform으로 디버깅 및 배포를 보완할 수 있습니다.&lt;/p&gt;

&lt;p&gt;다만 LangGraph는 API 계약을 검증하지 않습니다. 에이전트가 호출하는 API는 별도로 목업하고 테스트해야 합니다. 개발 비용을 줄이고 도구 호출의 신뢰성을 높이려면 &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;에서 엔드포인트를 목업하고 응답을 단언하세요. 에이전트가 실제 엔드포인트를 호출하기 전에 테스트 환경을 구성하려면 &lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 다운로드하십시오&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>gRPC API 테스트를 위한 최고의 grpcurl 대체 도구 (GUI 및 CLI)</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Thu, 25 Jun 2026 07:00:44 +0000</pubDate>
      <link>https://dev.to/rihpig/grpc-api-teseuteureul-wihan-coegoyi-grpcurl-daece-dogu-gui-mic-cli-1bk1</link>
      <guid>https://dev.to/rihpig/grpc-api-teseuteureul-wihan-coegoyi-grpcurl-daece-dogu-gui-mic-cli-1bk1</guid>
      <description>&lt;p&gt;grpcurl은 gRPC 서비스를 빠르게 확인할 때 가장 유용한 명령줄 도구입니다. 하지만 플래그가 많은 터미널 명령만으로는 API를 탐색하고, 스트리밍 호출을 재생하고, 팀원과 요청 예제를 공유하기 어렵습니다. &lt;a href="https://apidog.com/kr/blog/grpc-client?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;시각적인 gRPC 클라이언트&lt;/a&gt;나 저장된 요청, 환경 변수, 팀 워크플로우까지 지원하는 도구가 필요하다면 아래 6가지 grpcurl 대안을 검토해 보세요.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;지금 Apidog를 사용해 보세요&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  grpcurl이란 무엇이며, 언제 불편해지는가
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/fullstorydev/grpcurl" rel="noopener noreferrer"&gt;grpcurl&lt;/a&gt;은 gRPC용 &lt;code&gt;curl&lt;/code&gt;입니다. 서버 주소, 서비스 이름, 메서드 이름, JSON 요청 본문을 전달하면 gRPC 응답을 터미널에서 확인할 수 있습니다.&lt;/p&gt;

&lt;p&gt;기본 사용 흐름은 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;grpcurl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-plaintext&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"id": "123"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  localhost:50051 &lt;span class="se"&gt;\&lt;/span&gt;
  demo.UserService/GetUser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;서버 리플렉션이 켜져 있다면 &lt;code&gt;.proto&lt;/code&gt; 파일 없이도 서비스와 메서드를 탐색할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;grpcurl &lt;span class="nt"&gt;-plaintext&lt;/span&gt; localhost:50051 list
grpcurl &lt;span class="nt"&gt;-plaintext&lt;/span&gt; localhost:50051 list demo.UserService
grpcurl &lt;span class="nt"&gt;-plaintext&lt;/span&gt; localhost:50051 describe demo.UserService.GetUser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;리플렉션이 꺼져 있다면 &lt;code&gt;.proto&lt;/code&gt; 파일이나 프로토셋 디스크립터를 직접 지정해야 합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;grpcurl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-plaintext&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-import-path&lt;/span&gt; ./proto &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-proto&lt;/span&gt; user.proto &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"id": "123"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  localhost:50051 &lt;span class="se"&gt;\&lt;/span&gt;
  demo.UserService/GetUser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;grpcurl은 다음 작업에 특히 적합합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI에서 gRPC 헬스 체크 실행&lt;/li&gt;
&lt;li&gt;터미널에서 빠른 단항 호출 테스트&lt;/li&gt;
&lt;li&gt;셸 스크립트에 gRPC 호출 포함&lt;/li&gt;
&lt;li&gt;리플렉션으로 서비스 목록 확인&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;하지만 실제 개발 워크플로우에서는 다음 지점에서 불편해집니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLI 전용이라 익숙하지 않은 API를 탐색할 때 메서드와 메시지 구조를 직접 확인해야 합니다.&lt;/li&gt;
&lt;li&gt;클라이언트 스트리밍, 서버 스트리밍, 양방향 스트리밍은 stdin/stdout 기반이라 메시지 흐름을 시각적으로 보기 어렵습니다.&lt;/li&gt;
&lt;li&gt;요청 기록, 컬렉션, 환경 전환 기능이 내장되어 있지 않습니다.&lt;/li&gt;
&lt;li&gt;팀원에게 공유하려면 긴 명령어 문자열이나 별도 스크립트를 전달해야 합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;즉, grpcurl은 나쁜 도구가 아니라 목적이 명확한 도구입니다. 작업이 “단일 명령 실행”을 넘어 API 탐색, 반복 테스트, 협업으로 확장된다면 아래 대안이 더 적합합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  grpcurl 대안 한눈에 보기
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;도구&lt;/th&gt;
&lt;th&gt;인터페이스&lt;/th&gt;
&lt;th&gt;스트리밍 지원&lt;/th&gt;
&lt;th&gt;리플렉션&lt;/th&gt;
&lt;th&gt;최적의 용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Apidog&lt;/td&gt;
&lt;td&gt;GUI 데스크톱&lt;/td&gt;
&lt;td&gt;단항, 서버 스트리밍, 클라이언트 스트리밍, 양방향 스트리밍&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;td&gt;REST, GraphQL, 문서와 함께 gRPC를 시각적으로 테스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;grpcui&lt;/td&gt;
&lt;td&gt;웹 UI&lt;/td&gt;
&lt;td&gt;단항 + 스트리밍&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;td&gt;grpcurl 기반 브라우저 프런트엔드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postman&lt;/td&gt;
&lt;td&gt;GUI 데스크톱/웹&lt;/td&gt;
&lt;td&gt;단항 + 스트리밍&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;td&gt;이미 Postman을 사용하는 팀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kreya&lt;/td&gt;
&lt;td&gt;GUI 데스크톱&lt;/td&gt;
&lt;td&gt;단항 + 스트리밍&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;td&gt;gRPC 및 REST 중심 데스크톱 클라이언트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evans&lt;/td&gt;
&lt;td&gt;대화형 CLI&lt;/td&gt;
&lt;td&gt;단항 + 스트리밍&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;td&gt;REPL 스타일 터미널 워크플로우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BloomRPC&lt;/td&gt;
&lt;td&gt;GUI 데스크톱&lt;/td&gt;
&lt;td&gt;단항 + 스트리밍&lt;/td&gt;
&lt;td&gt;제한적&lt;/td&gt;
&lt;td&gt;레거시 프로젝트 유지보수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. Apidog: 시각적인 gRPC 클라이언트
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;는 REST, GraphQL, WebSocket, SOAP, gRPC를 하나의 데스크톱 앱에서 다루는 API 플랫폼입니다. gRPC만 별도 터미널에서 테스트하지 않고, 다른 API 프로토콜과 같은 작업 공간에서 관리할 수 있습니다.&lt;/p&gt;

&lt;p&gt;gRPC 테스트는 보통 다음 순서로 진행합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Apidog에서 새 gRPC 요청을 생성합니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.proto&lt;/code&gt; 파일을 가져오거나 서버 리플렉션으로 연결합니다.&lt;/li&gt;
&lt;li&gt;서비스와 메서드를 선택합니다.&lt;/li&gt;
&lt;li&gt;스키마 기반 요청 필드를 입력합니다.&lt;/li&gt;
&lt;li&gt;메타데이터, 인증, 서버 주소를 설정합니다.&lt;/li&gt;
&lt;li&gt;단항 또는 스트리밍 호출을 실행합니다.&lt;/li&gt;
&lt;li&gt;응답 메시지를 패널에서 확인하고 요청을 저장합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;grpcurl에서는 요청을 직접 JSON으로 작성해야 합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;grpcurl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-plaintext&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"user_id":"u_123","include_orders":true}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  localhost:50051 &lt;span class="se"&gt;\&lt;/span&gt;
  demo.UserService/GetUserProfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apidog에서는 같은 요청을 폼 기반 UI에서 작성하고 저장할 수 있습니다. &lt;code&gt;.proto&lt;/code&gt; 스키마를 기준으로 필드가 렌더링되므로 메시지 구조를 매번 &lt;code&gt;describe&lt;/code&gt;로 확인할 필요가 줄어듭니다.&lt;/p&gt;

&lt;p&gt;Apidog가 특히 유용한 경우는 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;서버 스트리밍 응답을 메시지 단위로 확인해야 할 때&lt;/li&gt;
&lt;li&gt;클라이언트 스트리밍 또는 양방향 스트리밍을 반복 테스트해야 할 때&lt;/li&gt;
&lt;li&gt;여러 서버 주소를 환경 변수로 전환해야 할 때&lt;/li&gt;
&lt;li&gt;인증 헤더나 메타데이터를 요청별로 재사용해야 할 때&lt;/li&gt;
&lt;li&gt;REST, GraphQL, gRPC를 같은 프로젝트에서 함께 관리해야 할 때&lt;/li&gt;
&lt;li&gt;팀원에게 실행 가능한 요청 예제를 공유해야 할 때&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;다만 Apidog는 GUI 클라이언트입니다. 셸 파이프라인에서 실행할 수 있는 바이너리가 필요하다면 grpcurl이나 Evans가 더 적합합니다. 반대로 API 탐색, 저장된 요청, 환경 변수, 팀 협업이 중요하다면 Apidog가 더 실용적입니다.&lt;/p&gt;

&lt;p&gt;여러 프로토콜을 함께 운영한다면 &lt;a href="https://apidog.com/kr/blog/how-to-build-multi-protocol-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;다중 프로토콜 API 워크플로우&lt;/a&gt;를 하나의 도구에서 관리하는 편이 더 단순합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 다운로드&lt;/a&gt;한 뒤 &lt;code&gt;.proto&lt;/code&gt; 파일을 가져오거나 리플렉션으로 연결해 첫 gRPC 호출을 실행해 보세요.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. grpcui: grpcurl에 가까운 웹 UI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/fullstorydev/grpcui" rel="noopener noreferrer"&gt;grpcui&lt;/a&gt;는 grpcurl과 같은 fullstorydev에서 만든 도구입니다. grpcurl의 동작 방식은 유지하면서 브라우저 기반 UI를 제공합니다.&lt;/p&gt;

&lt;p&gt;일반적인 실행 방식은 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;grpcui &lt;span class="nt"&gt;-plaintext&lt;/span&gt; localhost:50051
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;실행하면 로컬 웹 서버가 열리고 브라우저에서 다음 작업을 할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;서비스 선택&lt;/li&gt;
&lt;li&gt;메서드 선택&lt;/li&gt;
&lt;li&gt;요청 메시지 입력&lt;/li&gt;
&lt;li&gt;메타데이터 설정&lt;/li&gt;
&lt;li&gt;단항 및 스트리밍 호출 실행&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;리플렉션이 꺼져 있다면 &lt;code&gt;.proto&lt;/code&gt; 파일을 지정할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;grpcui &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-plaintext&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-import-path&lt;/span&gt; ./proto &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-proto&lt;/span&gt; user.proto &lt;span class="se"&gt;\&lt;/span&gt;
  localhost:50051
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;grpcui가 적합한 경우는 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;grpcurl의 기능은 유지하고 싶지만 브라우저 폼이 필요할 때&lt;/li&gt;
&lt;li&gt;로컬 개발 서버를 빠르게 탐색해야 할 때&lt;/li&gt;
&lt;li&gt;별도 API 플랫폼 없이 단순한 gRPC UI만 필요할 때&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;제한점도 명확합니다. grpcui는 gRPC 탐색에 집중된 단일 목적 도구입니다. REST 테스트, 장기적인 컬렉션 관리, 팀 작업 공간, 문서화 워크플로우까지 필요하다면 다른 도구가 더 적합합니다.&lt;/p&gt;

&lt;p&gt;설치 및 실행 방법은 &lt;a href="https://github.com/fullstorydev/grpcui" rel="noopener noreferrer"&gt;grpcui 저장소&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Postman: 기존 Postman 팀을 위한 선택지
&lt;/h2&gt;

&lt;p&gt;Postman은 gRPC 요청을 지원합니다. 이미 팀에서 Postman을 표준 API 도구로 사용하고 있다면, 별도 도구를 도입하기 전에 Postman의 gRPC 기능을 먼저 검토할 만합니다.&lt;/p&gt;

&lt;p&gt;기본 흐름은 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Postman에서 gRPC 요청을 생성합니다.&lt;/li&gt;
&lt;li&gt;서버 URL을 입력합니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.proto&lt;/code&gt; 파일을 로드하거나 리플렉션을 사용합니다.&lt;/li&gt;
&lt;li&gt;서비스와 메서드를 선택합니다.&lt;/li&gt;
&lt;li&gt;요청 메시지, 메타데이터, 인증 정보를 입력합니다.&lt;/li&gt;
&lt;li&gt;호출을 실행하고 응답을 확인합니다.&lt;/li&gt;
&lt;li&gt;요청을 컬렉션에 저장합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-455.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-455.png" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Postman의 장점은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;팀이 이미 익숙한 UI&lt;/li&gt;
&lt;li&gt;컬렉션 기반 요청 관리&lt;/li&gt;
&lt;li&gt;환경 변수 사용&lt;/li&gt;
&lt;li&gt;기존 REST 테스트 워크플로우와의 연결&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;주의할 점도 있습니다. Postman의 gRPC 경험은 전통적인 REST 경험에 비해 상대적으로 최근에 추가된 기능이며, 팀 규모나 사용 방식에 따라 계정, 클라우드 동기화, 가격 정책을 함께 고려해야 합니다.&lt;/p&gt;

&lt;p&gt;더 넓은 API 테스트 도구를 비교하려면 &lt;a href="https://apidog.com/kr/blog/best-postman-alternatives-for-api-testing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 테스트를 위한 Postman 대안&lt;/a&gt;도 참고할 수 있습니다. Postman의 현재 gRPC 기능은 공식 &lt;a href="https://learning.postman.com/docs/sending-requests/grpc/grpc-client-overview/" rel="noopener noreferrer"&gt;gRPC 문서&lt;/a&gt;에서 확인하세요.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Kreya: gRPC와 REST 중심 데스크톱 클라이언트
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kreya.app/" rel="noopener noreferrer"&gt;Kreya&lt;/a&gt;는 gRPC와 REST에 중점을 둔 데스크톱 API 클라이언트입니다. &lt;code&gt;.proto&lt;/code&gt; 파일과 서버 리플렉션을 지원하고, 스키마 기반 요청 폼을 생성하며, 단항 및 스트리밍 호출을 처리합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-456.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.apidog.com%2Fblog-next%2F2026%2F06%2Fimage-456.png" alt="" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kreya에서 일반적으로 수행하는 작업은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;프로젝트 생성&lt;/li&gt;
&lt;li&gt;gRPC 엔드포인트 추가&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.proto&lt;/code&gt; 파일 또는 리플렉션으로 서비스 로드&lt;/li&gt;
&lt;li&gt;요청 작성 및 실행&lt;/li&gt;
&lt;li&gt;환경 변수 구성&lt;/li&gt;
&lt;li&gt;요청 재사용&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kreya가 적합한 경우는 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gRPC와 REST만 집중적으로 테스트하고 싶을 때&lt;/li&gt;
&lt;li&gt;데스크톱 기반의 깔끔한 API 클라이언트가 필요할 때&lt;/li&gt;
&lt;li&gt;전체 API 플랫폼보다 가벼운 도구를 원할 때&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;반대로 모킹, 문서 생성, 설계 협업까지 한 번에 관리하려면 더 넓은 범위의 API 플랫폼이 필요할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Evans: REPL 스타일 대화형 CLI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ktr0731/evans" rel="noopener noreferrer"&gt;Evans&lt;/a&gt;는 터미널에서 사용하는 대화형 gRPC 클라이언트입니다. grpcurl처럼 CLI 기반이지만, 긴 명령어를 매번 작성하는 대신 REPL 방식으로 서비스와 메서드를 탐색합니다.&lt;/p&gt;

&lt;p&gt;예를 들어 다음처럼 실행할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;evans &lt;span class="nt"&gt;--host&lt;/span&gt; localhost &lt;span class="nt"&gt;--port&lt;/span&gt; 50051 &lt;span class="nt"&gt;--reflection&lt;/span&gt; repl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;REPL 안에서는 패키지, 서비스, 메서드를 선택하고 요청 값을 대화식으로 입력할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; show package
&amp;gt; package demo
&amp;gt; show service
&amp;gt; service UserService
&amp;gt; call GetUser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Evans가 적합한 경우는 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;터미널 기반 워크플로우를 유지하고 싶을 때&lt;/li&gt;
&lt;li&gt;grpcurl 명령어의 긴 플래그 입력이 부담스러울 때&lt;/li&gt;
&lt;li&gt;서버 리플렉션으로 빠르게 서비스 구조를 탐색하고 싶을 때&lt;/li&gt;
&lt;li&gt;GUI 없이 스트리밍 호출을 테스트해야 할 때&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Evans 역시 CLI 도구이므로 시각적인 스트리밍 패널이나 팀 작업 공간은 없습니다. 하지만 터미널 안에서 반복적으로 gRPC를 탐색해야 한다면 grpcurl보다 편한 경우가 많습니다.&lt;/p&gt;

&lt;p&gt;설치 방법은 &lt;a href="https://github.com/ktr0731/evans" rel="noopener noreferrer"&gt;Evans GitHub 저장소&lt;/a&gt;를 참고하세요.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. BloomRPC: 새 프로젝트에는 권장하지 않는 레거시 도구
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/bloomrpc/bloomrpc" rel="noopener noreferrer"&gt;BloomRPC&lt;/a&gt;는 한때 많이 사용되던 오픈 소스 gRPC GUI입니다. 데스크톱 앱에서 &lt;code&gt;.proto&lt;/code&gt; 파일을 불러오고, 메서드를 선택하고, 요청을 작성할 수 있었습니다.&lt;/p&gt;

&lt;p&gt;하지만 현재는 적극적으로 유지보수되지 않습니다. 따라서 다음 문제가 생길 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;최신 gRPC 기능 대응 부족&lt;/li&gt;
&lt;li&gt;의존성 업데이트 지연&lt;/li&gt;
&lt;li&gt;OS 호환성 문제&lt;/li&gt;
&lt;li&gt;보안 업데이트 부족&lt;/li&gt;
&lt;li&gt;팀 워크플로우 확장성 제한&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;새 프로젝트라면 BloomRPC를 선택하지 않는 것이 좋습니다. 이미 BloomRPC 기반 워크플로우를 물려받았다면 Apidog, grpcui, Postman, Kreya, Evans 중 하나로 마이그레이션 계획을 세우세요.&lt;/p&gt;

&lt;h2&gt;
  
  
  선택 기준
&lt;/h2&gt;

&lt;p&gt;사용 방식에 따라 도구를 선택하면 됩니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;시각적인 gRPC 테스트, 저장된 요청, 환경 변수, 팀 공유가 필요하다면:&lt;/strong&gt; Apidog&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;grpcurl에 가장 가까운 브라우저 UI가 필요하다면:&lt;/strong&gt; grpcui&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;이미 Postman을 표준으로 사용 중이라면:&lt;/strong&gt; Postman gRPC 지원&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;gRPC와 REST 중심의 데스크톱 클라이언트가 필요하다면:&lt;/strong&gt; Kreya&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;터미널에 머물되 대화형 탐색이 필요하다면:&lt;/strong&gt; Evans&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;기존 레거시 환경을 유지보수 중이라면:&lt;/strong&gt; BloomRPC 상태를 확인하고 마이그레이션 준비&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;gRPC를 처음부터 끝까지 테스트하는 워크플로우가 필요하다면 &lt;a href="https://apidog.com/kr/blog/test-grpc-apis?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;gRPC API를 효율적으로 테스트하는 방법&lt;/a&gt;을 참고하세요. 명령줄 중심으로 계속 작업한다면 &lt;a href="https://apidog.com/kr/blog/grpc-curl?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;grpc-curl 워크스루&lt;/a&gt;가 좋은 출발점입니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문
&lt;/h2&gt;

&lt;h3&gt;
  
  
  grpcurl의 GUI 버전이 있나요?
&lt;/h3&gt;

&lt;p&gt;가장 직접적인 GUI 대안은 grpcui입니다. grpcurl과 같은 계열의 도구이며, 리플렉션과 프로토 처리를 기반으로 브라우저 폼을 제공합니다.&lt;/p&gt;

&lt;p&gt;저장된 요청, 환경 변수, 시각적인 스트리밍 응답, 팀 작업 공간까지 필요하다면 Apidog 같은 데스크톱 gRPC 클라이언트가 더 적합합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  명령줄 없이 gRPC 스트리밍을 테스트할 수 있나요?
&lt;/h3&gt;

&lt;p&gt;네. Apidog, Postman, Kreya, grpcui는 UI를 통해 gRPC 스트리밍 호출을 지원합니다. 특히 서버 스트리밍의 경우 메시지가 도착하는 흐름을 패널에서 확인할 수 있습니다.&lt;/p&gt;

&lt;p&gt;grpcurl과 Evans도 스트리밍을 지원하지만, 메시지 입력과 출력이 터미널 기반입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  이 도구들은 &lt;code&gt;.proto&lt;/code&gt; 파일이 필요한가요?
&lt;/h3&gt;

&lt;p&gt;항상 필요한 것은 아닙니다. 서버 리플렉션이 활성화되어 있으면 클라이언트가 서비스와 메서드를 직접 검색할 수 있습니다.&lt;/p&gt;

&lt;p&gt;리플렉션이 꺼져 있다면 &lt;code&gt;.proto&lt;/code&gt; 파일이나 컴파일된 프로토셋을 제공해야 합니다. 대부분의 도구는 두 방식을 모두 지원합니다.&lt;/p&gt;

&lt;p&gt;API 테스트 전체 관점이 필요하다면 &lt;a href="https://apidog.com/kr/blog/api-testing-ultimate-guide?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 테스트 궁극 가이드&lt;/a&gt;에서 REST, gRPC, 기타 프로토콜의 테스트 방식을 함께 확인할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  grpcurl은 여전히 사용할 가치가 있나요?
&lt;/h3&gt;

&lt;p&gt;네. grpcurl은 여전히 다음 작업에 매우 적합합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI 파이프라인에서 gRPC 상태 확인&lt;/li&gt;
&lt;li&gt;셸 스크립트에 gRPC 호출 포함&lt;/li&gt;
&lt;li&gt;빠른 단항 요청 테스트&lt;/li&gt;
&lt;li&gt;리플렉션 기반 서비스 목록 확인&lt;/li&gt;
&lt;li&gt;터미널에서 일회성 디버깅&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;다만 반복적으로 요청을 저장하고, 스트리밍 응답을 시각적으로 보고, 팀원과 실행 가능한 예제를 공유해야 한다면 GUI 기반 대안을 함께 사용하는 것이 효율적입니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  결론
&lt;/h2&gt;

&lt;p&gt;grpcurl은 명령줄 gRPC 테스트에 강력한 도구입니다. 스크립트화된 호출, CI 체크, 빠른 터미널 디버깅에는 여전히 좋은 선택입니다.&lt;/p&gt;

&lt;p&gt;하지만 익숙하지 않은 서비스를 탐색하고, 스트리밍 메시지를 관찰하고, 요청을 저장하고, 팀과 공유해야 한다면 시각적인 gRPC 클라이언트가 더 빠릅니다. Apidog는 gRPC를 REST, GraphQL, 모킹, 문서화 워크플로우와 함께 관리할 수 있어 여러 프로토콜을 다루는 팀에 특히 유용합니다.&lt;/p&gt;

&lt;p&gt;단 하나의 플래그도 작성하지 않고 gRPC 서비스를 테스트하고 싶다면 &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 무료로 사용해보고&lt;/a&gt;, &lt;code&gt;.proto&lt;/code&gt; 파일을 가져오거나 리플렉션으로 연결해 GUI에서 단항 및 스트리밍 호출을 실행해 보세요.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
