<?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: treesoop</title>
    <description>The latest articles on DEV Community by treesoop (@treesoop_537fb02f65507c5e).</description>
    <link>https://dev.to/treesoop_537fb02f65507c5e</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%2F3985296%2F031f0eb4-d207-4b92-948a-8e3611c4ad4b.png</url>
      <title>DEV Community: treesoop</title>
      <link>https://dev.to/treesoop_537fb02f65507c5e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/treesoop_537fb02f65507c5e"/>
    <language>en</language>
    <item>
      <title>Claude Code Max 기반 업무 자동화 시스템 구축과 에이전트 연동 아키텍처</title>
      <dc:creator>treesoop</dc:creator>
      <pubDate>Wed, 08 Jul 2026 03:09:44 +0000</pubDate>
      <link>https://dev.to/treesoop_537fb02f65507c5e/claude-code-max-giban-eobmu-jadonghwa-siseutem-gucuggwa-eijeonteu-yeondong-akitegceo-11a3</link>
      <guid>https://dev.to/treesoop_537fb02f65507c5e/claude-code-max-giban-eobmu-jadonghwa-siseutem-gucuggwa-eijeonteu-yeondong-akitegceo-11a3</guid>
      <description>&lt;p&gt;Claude Code Max를 전 팀원이 표준 환경으로 쓰는 팀과, 프로젝트마다 AI 도구를 선택적으로 꺼내는 팀 사이에는 아키텍처 설계 단계부터 차이가 생긴다. 이 글은 멀티 에이전트 시스템을 실제로 구축할 때 어떤 구조로 에이전트를 연동하고, 어떤 기준으로 책임 경계를 나누는지를 엔지니어링 관점에서 정리한다.&lt;/p&gt;




&lt;h2&gt;
  
  
  전 팀원이 Claude Code Max를 표준 도구로 쓸 때 달라지는 것
&lt;/h2&gt;

&lt;p&gt;AI 도구를 개인 생산성 도구로만 쓰면 지식이 개인에게 갇힌다. Claude Code Max를 팀 표준으로 세팅하면 프롬프트, MCP 도구 체인, 에이전트 명세가 코드베이스에 함께 버전 관리된다.&lt;/p&gt;

&lt;p&gt;구체적으로 달라지는 점:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;컨텍스트 공유&lt;/strong&gt;: CLAUDE.md에 팀 공통 컨벤션과 도메인 용어를 정의해두면 모든 팀원의 세션이 같은 맥락에서 출발한다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP 도구 체인 표준화&lt;/strong&gt;: &lt;code&gt;mcp_servers&lt;/code&gt; 설정을 리포지토리에 커밋해두면 팀원이 로컬에서 동일한 MCP 서버 목록을 사용한다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;서브에이전트 재사용&lt;/strong&gt;: 한 팀원이 만든 서브에이전트 프롬프트를 다른 팀원이 그대로 호출할 수 있어, 중복 개발이 줄어든다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TDD 자동화&lt;/strong&gt;: Playwright MCP를 연결하면 E2E 테스트 시나리오 작성과 실행을 에이전트가 담당한다. 사람은 실패한 케이스만 리뷰한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;팀 단위로 AI를 쓴다는 것은 "모두가 똑같이 쓴다"가 아니라, "공유 가능한 AI 인프라를 팀이 함께 유지 관리한다"에 가깝다.&lt;/p&gt;




&lt;h2&gt;
  
  
  멀티 에이전트 시스템을 설계할 때 어떤 원칙으로 분리하는가?
&lt;/h2&gt;

&lt;p&gt;에이전트를 여러 개 연동할 때 가장 흔한 실수는 하나의 에이전트에 너무 많은 책임을 주는 것이다. 단일 LLM 호출로 "데이터 수집 → 분석 → 보고서 작성 → 슬랙 발송"을 한 번에 처리하려 하면, 중간 단계 오류가 전체 파이프라인을 무너뜨린다.&lt;/p&gt;

&lt;p&gt;멀티 에이전트 아키텍처의 핵심 원칙은 &lt;strong&gt;단일 책임 + 명시적 인터페이스&lt;/strong&gt;다.&lt;/p&gt;

&lt;p&gt;각 에이전트는 하나의 작업만 수행하고, 입출력 스키마를 JSON으로 명시한다. 오케스트레이터 에이전트는 실행 순서와 에러 핸들링만 담당한다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────┐
│         Orchestrator Agent          │
│  (실행 순서 결정 + 에러 핸들링)        │
└──────┬──────────┬──────────┬────────┘
       │          │          │
       ▼          ▼          ▼
  Collector    Analyzer   Reporter
   Agent        Agent      Agent
  (데이터 수집)  (분석·요약)  (보고서 생성)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;오케스트레이터가 서브에이전트를 호출할 때는 Claude의 서브에이전트 기능을 직접 활용할 수도 있고, 경량 태스크 큐(예: Celery, BullMQ)를 중간에 두어 비동기로 처리할 수도 있다. 동기 파이프라인이 필요한가, 비동기 병렬 처리가 필요한가에 따라 선택이 달라진다.&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;직접 서브에이전트 호출&lt;/td&gt;
&lt;td&gt;순차 의존성이 강한 파이프라인&lt;/td&gt;
&lt;td&gt;컨텍스트 윈도우 누적 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메시지 큐 (BullMQ 등)&lt;/td&gt;
&lt;td&gt;병렬 처리 또는 재시도 필요&lt;/td&gt;
&lt;td&gt;큐 인프라 운영 오버헤드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP API 호출&lt;/td&gt;
&lt;td&gt;에이전트가 독립 서비스로 분리된 경우&lt;/td&gt;
&lt;td&gt;네트워크 레이턴시, 인증 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP 프로토콜&lt;/td&gt;
&lt;td&gt;도구를 에이전트 간에 공유해야 할 때&lt;/td&gt;
&lt;td&gt;MCP 서버 별도 운영 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  연구에서 운영까지 한 팀이 전 주기를 책임지는 아키텍처 설계법
&lt;/h2&gt;

&lt;p&gt;R&amp;amp;D와 운영이 다른 팀이면 모델 연구 결과가 프로덕션에 반영되기까지 병목이 생긴다. 한 팀이 NLP 파인튜닝부터 에이전트 운영까지 맡을 때 아키텍처를 어떻게 구성할지가 실질적인 문제다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — 모델 레이어와 에이전트 레이어를 분리한다&lt;/strong&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 python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;MODEL_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;base_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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&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_MODEL&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;claude-opus-4-5&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;fine_tuned_endpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FINE_TUNED_ENDPOINT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&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_TEMPERATURE&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;0.2&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;이렇게 하면 R&amp;amp;D 팀이 파인튜닝 모델을 교체할 때 에이전트 코드 수정 없이 환경변수만 변경하면 된다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — 에이전트 상태를 외부 저장소에 위임한다&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;에이전트 메모리를 프로세스 내부에 두면 재시작 시 컨텍스트가 사라진다. Redis나 Postgres에 세션 상태를 저장하고, 에이전트는 무상태(stateless)로 설계한다.&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;redis&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6379&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;save_agent_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_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;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setex&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;agent:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&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;dumps&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_agent_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_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="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&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;agent:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="si"&gt;}&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="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;raw&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;raw&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3 — 오케스트레이터에 재시도 정책을 명시한다&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;서브에이전트가 실패할 때 오케스트레이터가 자동 재시도할 조건을 코드로 정의한다. 모호한 "에러 발생 시 재시도" 주석 대신, backoff 전략과 최대 시도 횟수를 명시한다.&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;tenacity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stop_after_attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wait_exponential&lt;/span&gt;

&lt;span class="nd"&gt;@retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;stop_after_attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;wait_exponential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier&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="nb"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;max&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="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_sub_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;agent_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;tenacity&lt;/code&gt;는 Python 재시도 라이브러리 중 가장 널리 쓰이는 선택지다. 실제 프로덕션에서 LLM API 호출은 간헐적 타임아웃이 발생하기 때문에 재시도 레이어가 없으면 운영 중 파이프라인이 의도치 않게 멈춘다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — 관측 가능성(observability)을 처음부터 설계에 포함한다&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;에이전트가 어떤 결정을 내렸는지 나중에 추적할 수 없으면 디버깅이 불가능하다. 각 에이전트 호출에 &lt;code&gt;trace_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;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&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;traced_agent_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;trace_id&lt;/span&gt; &lt;span class="o"&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;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trace_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;trace_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;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1000&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;ok&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="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trace_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;trace_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;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;e&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;fail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 패턴을 모든 에이전트에 데코레이터로 적용하면 분산 트레이싱 없이도 기본적인 감사 로그를 확보할 수 있다. OpenTelemetry를 붙이면 더 정교한 추적이 가능하다.&lt;/p&gt;




&lt;h2&gt;
  
  
  오픈소스 공개가 기술 신뢰를 어떻게 증명하는가?
&lt;/h2&gt;

&lt;p&gt;코드를 공개한다는 것은 숨길 것이 없다는 선언이다. TreeSoop이 운영하는 &lt;a href="https://github.com/treesoop/ai-news-mcp" rel="noopener noreferrer"&gt;&lt;code&gt;ai-news-mcp&lt;/code&gt;&lt;/a&gt; 등의 오픈소스 프로젝트는 MCP 서버를 어떻게 구현하는지 실제 코드 수준에서 확인할 수 있다. 코드를 먼저 보고 협업 여부를 판단하고 싶은 엔지니어라면 GitHub에서 직접 검토할 수 있다.&lt;/p&gt;

&lt;p&gt;기술 신뢰는 포트폴리오 슬라이드보다 실행 가능한 코드가 더 빠르게 만든다. 오픈소스 기여 이력이 있는 팀에게 의존성을 맡기는 것이 리스크가 낮은 이유는 그 팀의 코드 스타일, 문서화 수준, 이슈 대응 방식을 사전에 확인할 수 있기 때문이다.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  멀티 에이전트 시스템에서 오케스트레이터는 반드시 별도 에이전트로 분리해야 하는가?
&lt;/h3&gt;

&lt;p&gt;반드시 그럴 필요는 없다. 파이프라인이 2~3단계로 단순하다면 오케스트레이터 로직을 메인 프로세스에 두어도 충분하다. 에이전트 수가 5개 이상이거나 조건 분기가 복잡해지면 그때 분리를 검토한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code Max의 MCP와 자체 API 호출 방식 중 어떤 것을 선택해야 하는가?
&lt;/h3&gt;

&lt;p&gt;MCP는 도구 인터페이스를 표준화해 여러 에이전트가 같은 도구를 재사용할 때 유리하다. 단일 에이전트가 특정 API만 호출한다면 MCP 서버를 별도로 운영할 이유가 없다. 도구 공유 여부가 핵심 판단 기준이다.&lt;/p&gt;

&lt;h3&gt;
  
  
  온프레미스 배포와 클라우드 배포 중 에이전트 아키텍처 설계가 달라지는가?
&lt;/h3&gt;

&lt;p&gt;에이전트 로직 자체는 동일하다. 달라지는 것은 모델 엔드포인트 주소와 스토리지 레이어다. 앞서 설명한 환경변수 주입 방식을 사용하면 배포 환경이 바뀌어도 코드 수정 없이 전환할 수 있다.&lt;/p&gt;

&lt;h3&gt;
  
  
  에이전트 파이프라인의 비용을 어떻게 예측하고 통제하는가?
&lt;/h3&gt;

&lt;p&gt;LLM 호출 비용은 토큰 수에 비례한다. 각 에이전트 호출에 입력·출력 토큰을 로깅하고, 월별 예산 상한을 설정해 초과 시 알림을 보내는 모니터링 레이어를 추가한다. 오케스트레이터 단에서 불필요한 중간 결과를 컨텍스트에 누적하지 않는 것이 비용 통제의 기본이다.&lt;/p&gt;

&lt;h3&gt;
  
  
  에이전트 시스템 구축에 앞서 팀 내에서 먼저 검증해야 할 것은 무엇인가?
&lt;/h3&gt;

&lt;p&gt;단일 에이전트로 가장 반복적인 업무 하나를 자동화하는 것부터 시작한다. 이 단계에서 입출력 스키마 설계, 에러 핸들링 패턴, 로깅 구조를 확정한 뒤 멀티 에이전트로 확장한다. 처음부터 복잡한 오케스트레이션을 설계하면 디버깅 지점이 너무 많아진다.&lt;/p&gt;




&lt;p&gt;에이전트 아키텍처는 도구 선택보다 경계 설계가 먼저다. 어떤 LLM을 쓰든 에이전트의 책임 범위가 모호하면 운영 단계에서 반드시 문제가 생긴다. 구조를 먼저 설계하고, Claude Code Max 같은 AI-native 개발 환경을 팀 인프라로 편입하면 설계와 구현 사이의 간격이 좁아진다. 코드나 아키텍처에 대해 더 구체적인 논의가 필요하다면 &lt;a href="https://github.com/treesoop" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;이나 기술 문의 채널로 연락 주시면 된다.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;더 보기: &lt;a href="https://treesoop.com?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;treesoop.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>architecture</category>
      <category>claude</category>
      <category>mcp</category>
    </item>
    <item>
      <title>학벌보다 구현 능력: 연구 출신 팀이 AI 외주를 다르게 접근하는 방식</title>
      <dc:creator>treesoop</dc:creator>
      <pubDate>Sat, 27 Jun 2026 04:18:58 +0000</pubDate>
      <link>https://dev.to/treesoop_537fb02f65507c5e/hagbeolboda-guhyeon-neungryeog-yeongu-culsin-timi-ai-oejureul-dareuge-jeobgeunhaneun-bangsig-3pd3</link>
      <guid>https://dev.to/treesoop_537fb02f65507c5e/hagbeolboda-guhyeon-neungryeog-yeongu-culsin-timi-ai-oejureul-dareuge-jeobgeunhaneun-bangsig-3pd3</guid>
      <description>&lt;p&gt;POSTECH·KAIST·서울대 출신 엔지니어를 팀에 둔다고 AI 외주 업체의 기술력이 자동으로 보장되지는 않는다. 출신 학교는 신호일 뿐이고, 실제로 중요한 건 논문 수준의 기술을 상용 환경에서 돌아가게 만드는 구현 능력이다. 나무숲(TreeSoop)이 학력을 앞세우지 않는 이유도 거기 있다. 아래에서 이 팀이 기술을 다루는 방식, 설계 원칙, 그리고 속도를 내는 구조를 순서대로 설명한다.&lt;/p&gt;




&lt;h2&gt;
  
  
  논문 기술을 상용 프로덕트로 이식하는 과정이 왜 어려운가?
&lt;/h2&gt;

&lt;p&gt;학술 논문에서 구현된 모델은 대부분 통제된 데이터셋 위에서 작동한다. 실제 고객 환경은 다르다. 노이즈가 섞인 입력, 예외 케이스, 지연 시간 제약, 운영 비용 — 이 변수들이 논문 코드를 그대로 올려놓으면 쓸 수 없게 만드는 요인이다.&lt;/p&gt;

&lt;p&gt;연구 출신 엔지니어의 실질적 가치는 "논문을 읽는 능력"이 아니라 &lt;strong&gt;그 논문이 어느 가정 위에서 작동하는지를 파악하고, 상용 환경에 맞게 재설계하는 판단력&lt;/strong&gt;이다. 예를 들어 컴퓨터 비전 기반 도면 부품 추출 시스템을 구축할 때, 학술 벤치마크 기준으로 훈련된 탐지 모델을 산업 도면에 적용하면 초기에는 거의 작동하지 않는다. 해상도 차이, 레이블 불일치, 추론 속도 — 세 가지를 동시에 잡는 파이프라인을 설계하는 과정이 진짜 구현이다.&lt;/p&gt;

&lt;p&gt;나무숲이 R&amp;amp;D 프로젝트 12건을 쌓아온 방식도 같다. 자연어 처리, 컴퓨터 비전, 신호 처리, 공간 AI 각 도메인에서 논문 기술을 실제 운영 환경까지 끌고 간 경험이 반복되면서 "어디서 막히는지"를 미리 안다. 이 경험이 설계 초기에 기술 부채를 줄이는 결정을 내리게 한다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://treesoop.com/services/agentic-ai" rel="noopener noreferrer"&gt;에이전트 기반 AI 자동화 시스템&lt;/a&gt;을 구축할 때도 같은 원칙이 적용된다. 단일 LLM 호출이 아니라 여러 에이전트가 협업하는 구조를 만들 때, 각 에이전트의 역할 경계와 실패 복구 로직을 얼마나 정밀하게 설계하느냐가 운영 단계의 안정성을 결정한다.&lt;/p&gt;




&lt;h2&gt;
  
  
  기술 부채를 설계 단계에서 줄이는 방법은?
&lt;/h2&gt;

&lt;p&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; "일단 돌아가면 된다"는 마인드로 외부 라이브러리를 무분별하게 쌓으면 6개월 뒤 의존성 충돌이 온다. 핵심 기능에 필요한 최소 의존성만 유지한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 원칙들은 교과서에도 나오지만, 실제로 지키는 팀과 아닌 팀의 차이는 프로젝트 12개월 시점에 드러난다. 연구 환경에서 재현성과 구조화에 훈련된 엔지니어는 이 습관을 상용 코드에도 그대로 적용한다.&lt;/p&gt;




&lt;h2&gt;
  
  
  오픈소스 기여가 기술력의 실질적 근거가 되는 이유
&lt;/h2&gt;

&lt;p&gt;기술력을 주장하는 방법 중 가장 검증하기 쉬운 건 공개된 코드다. 나무숲의 오픈소스 자산은 현재 GitHub 누적 ★120+ 수준이다. 숫자 자체보다 중요한 건 그 자산이 만들어진 방식이다.&lt;/p&gt;

&lt;p&gt;★는 다른 개발자들이 "쓸 만하다"고 판단했을 때 붙인다. 사내에서만 쓰는 코드는 외부 검증을 받지 않는다. 공개 저장소에 올린 코드는 이슈, PR, 포크 형태로 실제 사용 여부가 드러난다. 이 과정이 코드 품질에 대한 외부 피드백 루프 역할을 한다.&lt;/p&gt;

&lt;p&gt;AI R&amp;amp;D 파트너십 12건 역시 같은 맥락에서 본다. 각 프로젝트는 특정 기술 도메인의 문제를 실제로 풀어야 했고, 그 과정에서 나온 공통 모듈과 유틸리티가 오픈소스로 쌓였다. 리서치만 하거나 개발만 하는 팀이 아니라, 연구에서 운영까지 한 팀이 끝까지 책임지는 구조이기 때문에 실제로 돌아가는 코드가 나올 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://treesoop.com/cases/emotion-ai" rel="noopener noreferrer"&gt;감정 AI 프로젝트&lt;/a&gt;처럼 신호 처리와 딥러닝이 교차하는 영역에서도, 학술 구현체를 실제 서비스 환경으로 옮기는 과정에서 재사용 가능한 컴포넌트가 생긴다. 이것이 다음 프로젝트의 착수 속도를 높이는 기반이 된다.&lt;/p&gt;




&lt;h2&gt;
  
  
  8인 팀이 대형 SI보다 빠르게 착수하는 구조적 이유는?
&lt;/h2&gt;

&lt;p&gt;규모가 작으면 느릴 것 같지만, 실제로는 반대인 경우가 많다. 이유는 단순하다. 의사결정 단계가 적고, 표준 환경이 통일되어 있으며, 팀원 전원이 같은 도구를 쓴다.&lt;/p&gt;

&lt;p&gt;나무숲의 경우 팀원 8명(창업자 2, 엔지니어 3, 디자이너 2, QA 1) 전원이 Claude Code Max를 표준 개발 환경으로 쓴다. "AI를 쓰는 팀원도 있고 아닌 팀원도 있는" 구조가 아니다. 전원이 같은 환경을 기본으로 쓰기 때문에 코드 리뷰, 디버깅, 문서 작성 사이클이 빠르게 돌아간다.&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;대형 SI / 전통 외주&lt;/th&gt;
&lt;th&gt;나무숲(8인 AI 표준 팀)&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;상담 후 2주 내 착수 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 도구 채택 방식&lt;/td&gt;
&lt;td&gt;팀원 개인 재량 또는 미채택&lt;/td&gt;
&lt;td&gt;팀 전원 Claude Code Max 표준 적용&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;R&amp;amp;D → 운영 이관&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;최소 3천만원~, 필요 범위에 맞게&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;속도는 도구 하나로 나오지 않는다. 도구가 팀 전체에 균일하게 배포되고, 그 위에서 일하는 방식이 통일되어야 속도가 나온다. 이것이 "전원이 AI를 기본 도구로 쓴다"는 말의 실질적 의미다.&lt;/p&gt;

&lt;p&gt;착수 속도와 개발 환경에 관심이 있다면 &lt;a href="https://treesoop.com/services/ai-automation" rel="noopener noreferrer"&gt;AI 자동화 서비스 페이지&lt;/a&gt;에서 실제 구축 범위를 확인할 수 있다.&lt;/p&gt;




&lt;h2&gt;
  
  
  단계별로 보는 AI 외주 프로젝트 검증 체크리스트
&lt;/h2&gt;

&lt;p&gt;기술력 있는 AI 외주 팀을 고를 때 실제로 확인해야 하는 항목이다. 학력이나 수상 이력 대신 아래를 보는 게 낫다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — 공개 코드 확인&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub 저장소가 있는가. 있다면 최근 커밋 날짜, 이슈 대응 방식, 코드 구조를 본다. 별점 수보다 실제 사용 흔적(포크, 이슈, PR 병합 이력)이 더 신뢰할 만한 지표다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — R&amp;amp;D와 상용 구현 이력의 비율 확인&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;리서치 논문만 있는 팀과 논문 기술을 실제로 배포해본 팀은 다르다. 포트폴리오에 "실제 운영 중인 시스템"이 있는지 확인한다. 데모와 운영 환경은 다르다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — 기술 스택의 일관성 확인&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;프로젝트마다 스택이 크게 달라지면 팀의 핵심 역량이 불명확하다는 신호일 수 있다. 도메인 특화 경험이 쌓이려면 특정 기술 영역에서 반복 구현 이력이 있어야 한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — 팀 구성과 의사결정 구조 확인&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;R&amp;amp;D를 담당하는 사람과 운영 코드를 짜는 사람이 같은 팀인가, 아니면 따로인가. 연구-개발-운영이 분리된 팀은 이관 시 맥락 손실이 크다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — 착수 이후 커뮤니케이션 방식 확인&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;주간 보고 구조가 있는가, 이슈 추적 도구를 공유하는가. 작은 팀일수록 이 부분이 계약서보다 더 직접적으로 프로젝트 품질에 영향을 준다.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  연구 출신 팀이 일반 AI 외주와 비교해 기술 비용이 더 비싼가?
&lt;/h3&gt;

&lt;p&gt;반드시 그렇지는 않다. 초기 설계에서 기술 부채를 줄이면 중간에 재작업하는 비용이 줄어든다. 나무숲의 경우 최소 3천만원~으로 시작하며, 필요한 기능 범위에 맞게 협의한다. 단기적으로 단가가 낮은 팀이 장기적으로 더 비싸지는 경우가 있다.&lt;/p&gt;

&lt;h3&gt;
  
  
  오픈소스 기여가 실제 프로젝트 품질과 어떤 관련이 있나?
&lt;/h3&gt;

&lt;p&gt;오픈소스에 공개된 코드는 외부 개발자들의 검토를 받는다. 이 과정이 코드 품질에 대한 외부 피드백 루프로 작동하며, 사내에서만 쓰는 코드보다 구조화 압력이 높다. 공개 저장소에서 실제 사용 흔적을 확인하면 팀의 코드 수준을 간접적으로 가늠할 수 있다.&lt;/p&gt;

&lt;h3&gt;
  
  
  R&amp;amp;D 프로젝트와 일반 AI 개발 외주는 계약 방식이 다른가?
&lt;/h3&gt;

&lt;p&gt;R&amp;amp;D 성격의 프로젝트는 결과 스펙이 사전에 고정되기 어렵다. 나무숲은 R&amp;amp;D(12건)와 일반 외주(19건) 모두 경험이 있으며, 프로젝트 성격에 따라 마일스톤 방식과 고정 범위 방식을 나눠 적용한다. 초기 상담에서 어떤 구조가 맞는지 먼저 확인한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  팀 규모가 작으면 대형 프로젝트를 감당할 수 없지 않나?
&lt;/h3&gt;

&lt;p&gt;팀 규모가 작으면 감당하기 어려운 프로젝트 유형이 있다. 다만 AI 외주에서 규모보다 더 중요한 건 기술 범위의 적합성이다. 나무숲은 자연어, 컴퓨터 비전, 신호 처리, 공간 AI를 포함한 R&amp;amp;D와 에이전트 자동화를 8인 팀 내에서 처리한다. 맞지 않는 규모의 프로젝트는 초기 상담에서 솔직하게 말한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI 챗봇 개발도 R&amp;amp;D 팀이 담당하는가?
&lt;/h3&gt;

&lt;p&gt;담당한다. &lt;a href="https://treesoop.com/services/ai-chatbot" rel="noopener noreferrer"&gt;AI 챗봇 개발&lt;/a&gt; 역시 단순 API 래핑이 아니라 도메인 특화 지식 연결, 멀티턴 문맥 관리, 운영 환경 최적화까지 설계가 필요한 작업이다. 동일한 팀이 연구-개발-운영 전 단계를 담당한다.&lt;/p&gt;




&lt;p&gt;출신 학교는 기술력의 필요 조건이 아니라 가능성의 신호다. 실제로 중요한 건 논문 기술을 상용 환경에서 작동하게 만드는 구현 경험, 기술 부채를 처음부터 줄이는 설계 습관, 그리고 공개 코드로 검증 가능한 이력이다. AI R&amp;amp;D 파트너십이나 상용 시스템 구축을 고민하고 있다면 &lt;a href="https://treesoop.com/services/ax-consulting" rel="noopener noreferrer"&gt;나무숲 기술 문의&lt;/a&gt;에서 구체적인 범위를 먼저 확인하는 것이 빠르다.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;더 보기: &lt;a href="https://treesoop.com?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;treesoop.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>여러 에이전트가 협업하는 업무 자동화 시스템 설계 방법</title>
      <dc:creator>treesoop</dc:creator>
      <pubDate>Mon, 22 Jun 2026 00:53:17 +0000</pubDate>
      <link>https://dev.to/treesoop_537fb02f65507c5e/yeoreo-eijeonteuga-hyeobeobhaneun-eobmu-jadonghwa-siseutem-seolgye-bangbeob-hik</link>
      <guid>https://dev.to/treesoop_537fb02f65507c5e/yeoreo-eijeonteuga-hyeobeobhaneun-eobmu-jadonghwa-siseutem-seolgye-bangbeob-hik</guid>
      <description>&lt;p&gt;업무 자동화 시스템을 만들 때 가장 먼저 드는 질문이 있다. "에이전트 하나면 안 되나?" 단일 에이전트로 시작하면 구조가 단순하고 디버깅도 쉽다. 그런데 실제 업무 맥락에서는 단일 에이전트가 빠르게 벽에 부딪힌다. 이 글은 왜 여러 에이전트가 협업하는 구조가 필요한지, 그리고 그 구조를 실제로 어떻게 설계하는지를 기술적으로 짚는다.&lt;/p&gt;




&lt;h2&gt;
  
  
  단일 에이전트로 충분하지 않은 이유
&lt;/h2&gt;

&lt;p&gt;단일 에이전트가 실패하는 지점은 복잡한 기능 탓이 아니라 컨텍스트 길이와 직렬 실행의 구조적 한계 때문이다.&lt;/p&gt;

&lt;p&gt;LLM 기반 에이전트에 하나의 긴 작업을 맡기면 세 가지 문제가 동시에 발생한다. 첫째, 컨텍스트 창이 소진된다. 데이터 수집, 변환, 검증, 발행을 하나의 루프에서 처리하면 중간 상태가 프롬프트에 누적되고, 모델은 앞부분 지시를 잊는다. 둘째, 직렬 실행은 병목을 만든다. API 호출이 5개 있고 각각 2초라면, 단일 에이전트는 10초를 기다린다. 셋째, 에러 격리가 불가능하다. 한 단계가 실패하면 전체 루프를 재시작해야 한다.&lt;/p&gt;

&lt;p&gt;반면 여러 에이전트가 협업하는 구조에서는 각 에이전트가 명확한 책임 경계를 갖는다. 한 에이전트가 데이터를 수집하고, 다른 에이전트가 변환하고, 또 다른 에이전트가 검증한다. 에러는 해당 에이전트 범위 안에서 처리되고, 독립된 작업은 병렬로 돌린다.&lt;/p&gt;




&lt;h2&gt;
  
  
  에이전트 협업 구조를 어떻게 설계할까?
&lt;/h2&gt;

&lt;p&gt;나무숲이 실제 자동화 프로젝트에서 가져가는 구조는 오케스트레이터-워커(Orchestrator-Worker) 패턴이다. 이 패턴은 Anthropic이 공개한 &lt;a href="https://docs.anthropic.com/en/docs/build-with-claude/agents" rel="noopener noreferrer"&gt;에이전트 설계 가이드라인&lt;/a&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;오케스트레이터&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;예를 들어 콘텐츠 자동 발행 파이프라인이라면, 오케스트레이터가 "오늘 발행할 항목 목록"을 받아 수집 워커, 요약 워커, 포맷 워커를 순서대로 호출한다. 검증 에이전트는 포맷 워커 출력을 보고 발행 가능 여부를 판단한다.&lt;/p&gt;




&lt;h2&gt;
  
  
  에이전트 간 데이터 흐름과 오케스트레이션 설계
&lt;/h2&gt;

&lt;p&gt;오케스트레이터는 각 워커를 직접 호출하고, 그 결과를 다음 워커의 입력으로 넘긴다. 이때 중요한 설계 결정이 두 가지다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;메시지 구조를 명시적으로 정의한다.&lt;/strong&gt; 에이전트 간 데이터는 자유형 텍스트가 아니라 스키마가 있는 구조로 전달해야 한다. JSON 스키마나 Pydantic 모델을 쓰면 에이전트 출력이 다음 에이전트의 입력 형식을 충족하는지 런타임 전에 검사할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;오케스트레이터는 작업의 의미를 이해하지 않는다.&lt;/strong&gt; 좋은 오케스트레이터는 라우터에 가깝다. "이 입력은 A 워커에게, 그 결과는 B 워커에게"를 결정할 뿐, 각 작업의 도메인 로직에 관여하지 않는다. 이 원칙을 지키면 워커를 교체하거나 추가할 때 오케스트레이터 코드를 손댈 필요가 없다.&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;anthropic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Anthropic&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="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WorkerOutput&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;       &lt;span class="c1"&gt;# "success" | "retry" | "failed"
&lt;/span&gt;    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;
    &lt;span class="n"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_prompt&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;user_input&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="n"&gt;WorkerOutput&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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;claude-opus-4-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&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;role&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&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 실제 구현에서는 tool_use 결과를 파싱
&lt;/span&gt;    &lt;span class="n"&gt;raw&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;content&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;text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;WorkerOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&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;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;raw&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;orchestrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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="c1"&gt;# 1단계: 수집 워커
&lt;/span&gt;    &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COLLECTOR_SYSTEM&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;task&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;collected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&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;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# 2단계: 변환 워커 (수집 결과를 입력으로)
&lt;/span&gt;    &lt;span class="n"&gt;transformed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRANSFORMER_SYSTEM&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;collected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&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;transformed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&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;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transformed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# 3단계: 검증 에이전트
&lt;/span&gt;    &lt;span class="n"&gt;validated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VALIDATOR_SYSTEM&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;transformed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 구조에서 각 &lt;code&gt;call_worker&lt;/code&gt; 호출은 독립된 LLM 호출이다. 오케스트레이터는 반환값의 &lt;code&gt;status&lt;/code&gt; 필드를 보고 다음 단계 진행 여부를 결정한다.&lt;/p&gt;

&lt;p&gt;병렬 실행이 필요한 경우, 독립된 워커는 &lt;code&gt;asyncio.gather&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;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&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;orchestrate_parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&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;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;call_worker_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PROCESSOR_SYSTEM&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;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&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="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_exceptions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WorkerOutput&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="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;r&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  기술적 난제: 상태 공유, 충돌 해결, 에러 복구
&lt;/h2&gt;

&lt;p&gt;이 세 가지는 다중 에이전트 시스템에서 실제로 가장 많이 틀리는 지점이다.&lt;/p&gt;

&lt;h3&gt;
  
  
  상태 공유를 어떻게 다룰까?
&lt;/h3&gt;

&lt;p&gt;에이전트 간 상태를 LLM 컨텍스트 안에서만 유지하면 컨텍스트가 늘어날수록 비용이 증가하고 정보가 유실된다. 실용적인 방법은 외부 상태 저장소를 두는 것이다. Redis나 간단한 PostgreSQL 테이블로 작업 상태를 기록하고, 각 에이전트는 시작 시 상태를 읽고 종료 시 갱신한다.&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="c1"&gt;# 상태를 외부 저장소에서 읽고 쓰는 패턴
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_task_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hgetall&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;task:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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;update_task_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_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;updates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hset&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;task:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expire&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;task:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 24시간 TTL
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  충돌 해결
&lt;/h3&gt;

&lt;p&gt;여러 워커가 동일한 리소스를 동시에 수정할 가능성이 있으면, 낙관적 잠금(optimistic locking)이나 단순한 큐 기반 직렬화로 처리한다. 에이전트가 AI 모델이라는 사실이 이 문제를 특별하게 만들지 않는다. 동시성 제어는 일반 분산 시스템과 같은 방식으로 접근한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  에러 복구 전략
&lt;/h3&gt;

&lt;p&gt;에러 복구는 재시도 정책을 명시적으로 설계해야 한다. 세 가지 수준으로 나눠 생각한다:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;즉시 재시도&lt;/strong&gt;: 일시적 네트워크 오류. 지수 백오프(exponential backoff)로 최대 3회.&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;/p&gt;




&lt;h2&gt;
  
  
  CTO가 납득할 수 있는 성능 지표와 안정성 검증
&lt;/h2&gt;

&lt;p&gt;"AI가 잘 한다"는 말은 CTO에게 아무 정보도 주지 않는다. 측정 가능한 지표로 시스템을 평가해야 한다.&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;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;/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;SLA 요구사항에 맞게 조정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;재시도 비율&lt;/td&gt;
&lt;td&gt;1회 이상 재시도한 작업 / 전체 작업&lt;/td&gt;
&lt;td&gt;높으면 워커 출력 스키마 또는 프롬프트 불안정 신호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비용 per task&lt;/td&gt;
&lt;td&gt;LLM API 호출 토큰 합산&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;strong&gt;골든 셋 테스트&lt;/strong&gt;다. 실제 운영 전 입력값과 기대 출력값의 쌍을 수십 건 만들어두고, 프롬프트나 모델 버전이 바뀔 때마다 이 셋에 대해 자동으로 회귀 테스트를 돌린다. LLM 출력은 비결정적이므로, 정확한 텍스트 매칭이 아니라 구조적 조건(필드 존재 여부, 값 범위, 포맷)으로 검증한다.&lt;/p&gt;

&lt;p&gt;모니터링은 LangSmith, LangFuse 같은 LLM 옵저버빌리티 도구를 쓰거나, 자체 로깅 파이프라인을 OpenTelemetry 형식으로 구축하면 각 에이전트 호출의 입력, 출력, 토큰, 지연 시간을 추적할 수 있다.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  에이전트 수가 많아질수록 비용이 선형으로 늘어나지 않나?
&lt;/h3&gt;

&lt;p&gt;LLM 호출 비용은 토큰 기준이다. 단일 에이전트에 모든 맥락을 넣으면 프롬프트가 비대해지고 오히려 비용이 높아진다. 각 워커가 자신의 역할에 필요한 컨텍스트만 받으면 워커 수가 늘어도 전체 토큰 사용량은 단일 에이전트 대비 비슷하거나 낮아지는 경우가 많다.&lt;/p&gt;

&lt;h3&gt;
  
  
  프롬프트 엔지니어링만으로 단일 에이전트를 개선하면 안 되나?
&lt;/h3&gt;

&lt;p&gt;단순한 작업에서는 그 편이 낫다. 다중 에이전트 구조가 필요한 기준은 명확하다. 작업 단계가 독립적으로 병렬 처리 가능하거나, 한 단계 실패가 전체 재시작을 유발하거나, 작업 유형마다 다른 도구 집합이 필요하면 구조를 나눠야 한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  오케스트레이터 자체가 LLM이어야 하나?
&lt;/h3&gt;

&lt;p&gt;반드시 그럴 필요는 없다. 작업 흐름이 사전에 결정 가능하면 오케스트레이터는 일반 파이썬 코드로 구현하는 편이 더 예측 가능하고 빠르다. LLM 오케스트레이터는 실행 중 상황을 보고 다음 워커를 동적으로 결정해야 할 때만 도입한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  에이전트 간 통신 방식으로 어떤 걸 쓸까?
&lt;/h3&gt;

&lt;p&gt;함수 호출(직접 호출), 메시지 큐(RabbitMQ, Kafka), REST/gRPC 세 가지 중 팀 규모와 에이전트 수에 맞게 고른다. 에이전트가 5개 이하이고 동일 프로세스에 있으면 함수 호출이 가장 단순하다. 에이전트가 별도 서비스로 분리되면 메시지 큐가 에러 격리와 재처리에 유리하다.&lt;/p&gt;

&lt;h3&gt;
  
  
  골든 셋 테스트의 적정 규모는?
&lt;/h3&gt;

&lt;p&gt;도메인에 따라 다르지만 최소 30~50건이 실용적인 출발점이다. 엣지 케이스와 실패 케이스를 의도적으로 포함시켜야 한다. 100건을 넘기면 테스트 실행 비용이 높아지므로, 핵심 경로를 커버하는 케이스를 선별해 관리하는 것이 현실적이다.&lt;/p&gt;




&lt;p&gt;에이전트 협업 시스템은 구조 설계 단계에서 방향이 거의 결정된다. 오케스트레이터와 워커의 책임 경계, 상태 관리 방식, 에러 복구 경로를 처음부터 명시적으로 설계하면 나중에 구조를 뜯어고치는 일을 피할 수 있다. 나무숲은 이 구조를 자체 자동화 파이프라인에 직접 적용해 운영하고 있다. 특정 구조나 코드 수준의 기술 검토가 필요하다면 &lt;a href="https://potenlab.dev/contact" rel="noopener noreferrer"&gt;포텐랩 기술 상담&lt;/a&gt;에서 구체적인 논의를 시작할 수 있다.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;더 보기: &lt;a href="https://treesoop.com?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;treesoop.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>architecture</category>
      <category>automation</category>
      <category>llm</category>
    </item>
    <item>
      <title>실패 없는 AI 개발 외주 업체를 고르는 CTO의 기준: 먼저 확인할 기술 체크리스트</title>
      <dc:creator>treesoop</dc:creator>
      <pubDate>Sun, 21 Jun 2026 02:29:37 +0000</pubDate>
      <link>https://dev.to/treesoop_537fb02f65507c5e/silpae-eobsneun-ai-gaebal-oeju-eobcereul-goreuneun-ctoyi-gijun-meonjeo-hwaginhal-gisul-cekeuriseuteu-11m3</link>
      <guid>https://dev.to/treesoop_537fb02f65507c5e/silpae-eobsneun-ai-gaebal-oeju-eobcereul-goreuneun-ctoyi-gijun-meonjeo-hwaginhal-gisul-cekeuriseuteu-11m3</guid>
      <description>&lt;p&gt;AI 개발 외주를 결정할 때 가장 먼저 흔들리는 건 기술 신뢰다. "혁신적인 AI 솔루션"이라는 말은 영업 자료마다 가득하지만, 실제로 논문 속 모델을 운영 환경에 올려본 팀인지, 아닌지는 몇 가지 질문만으로 드러난다. 이 글은 그 질문을 어떻게 던지고 무엇을 확인해야 하는지를 다룬다.&lt;/p&gt;




&lt;h2&gt;
  
  
  CTO가 가장 먼저 보는 것 — 과장 탐지
&lt;/h2&gt;

&lt;p&gt;기술 담당자가 영업 미팅에서 제일 먼저 하는 일은 과장 탐지다. "AI 기반", "딥러닝 활용", "최첨단 아키텍처" 같은 표현은 실질적 정보가 없다. 실제로 물어볼 수 있는 건 이렇다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;어떤 모델을 썼는가? Fine-tuning인가, RAG인가, 프롬프트 엔지니어링인가?&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;오픈소스 기여 이력은 좋은 신호다. 공개된 코드는 숨길 수 없다. GitHub에서 해당 팀의 레포지토리를 보면 실제로 어떤 수준에서 작업했는지 금방 확인된다. 커밋 히스토리, 이슈 논의 방식, 테스트 커버리지—이것들이 포트폴리오 PDF보다 훨씬 솔직하다.&lt;/p&gt;




&lt;h2&gt;
  
  
  구현 능력은 어떻게 검증할 수 있을까?
&lt;/h2&gt;

&lt;p&gt;기술 신뢰를 말이 아니라 코드로 확인하는 방법이 있다. 계약 전 기술 검증 세션을 요청하는 것이다. 30~60분짜리 기술 미팅에서 다음 두 가지를 해달라고 하면 된다.&lt;/p&gt;

&lt;p&gt;첫째, 최근 완료한 프로젝트의 아키텍처 다이어그램을 화이트보드에서 설명해달라. 실제 구현한 팀이라면 왜 그 구조를 선택했는지, 어떤 트레이드오프가 있었는지를 즉석에서 말한다. 외주를 다시 재외주한 팀이라면 설명에 빈틈이 생긴다.&lt;/p&gt;

&lt;p&gt;둘째, 비슷한 문제를 어떻게 접근할지 라이브로 보여달라. 예를 들어 문서에서 특정 정보를 추출하는 파이프라인을 짧게 스케치해달라고 하면 된다. 의사코드 수준이라도 상관없다. 여기서 LangChain을 쓸지 직접 구현할지, 왜 그런 선택을 하는지를 들으면 충분하다.&lt;/p&gt;

&lt;p&gt;아래는 실제 검증 과정에서 AI 문서 추출 파이프라인의 접근 방식을 확인할 때 쓸 수 있는 의사코드 예시다. 업체에게 이 정도 수준의 설명을 요구하는 것 자체가 이미 필터 역할을 한다.&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="c1"&gt;# 문서 추출 파이프라인 — 접근 방식 확인용 예시
# 외주 업체가 이 구조의 선택 이유를 설명할 수 있는지 보는 것이 목적
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_from_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extraction_schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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;
    접근 방식 1: LLM 직접 호출 (비용 높음, 유연성 높음)
    접근 방식 2: 구조화된 파서 + LLM fallback (비용 낮음, 정형 문서에 적합)
    접근 방식 3: Fine-tuned 모델 (초기 비용 높음, 반복 비용 낮음)

    선택 기준: 문서 다양성, 처리 볼륨, 정확도 요구 수준
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;parsed_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# pdfplumber 또는 pymupdf
&lt;/span&gt;
    &lt;span class="c1"&gt;# 구조화된 필드는 규칙 기반으로 먼저 처리
&lt;/span&gt;    &lt;span class="n"&gt;structured_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rule_based_extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extraction_schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 비정형 필드만 LLM에 위임
&lt;/span&gt;    &lt;span class="n"&gt;unstructured_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;llm_extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;parsed_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;extraction_schema&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;structured_fields&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;claude-3-5-sonnet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;structured_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;unstructured_fields&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;AI 개발에서 가장 흔한 실패 패턴 중 하나는 R&amp;amp;D팀과 개발팀이 분리된 구조다. 연구팀이 PoC를 만들고 개발팀에 넘기는 순간, 두 가지가 사라진다. 맥락과 책임이다.&lt;/p&gt;

&lt;p&gt;맥락이 사라지면 모델 선택의 이유, 데이터 전처리 결정, 성능 트레이드오프 같은 암묵지가 전달되지 않는다. 개발팀은 코드를 받아 운영하지만 왜 그렇게 짰는지를 모른다. 장애가 나면 원인을 찾는 데 시간이 두 배 걸린다.&lt;/p&gt;

&lt;p&gt;책임이 사라지면 품질 기준이 낮아진다. "PoC에서는 됐는데 운영에서 왜 안 되나요?"라는 상황이 만들어진다. 이건 구조 문제다. 운영 환경의 노이즈, 실제 데이터 분포, 엣지 케이스는 PoC 단계에서 경험한 팀만 대비할 수 있다.&lt;/p&gt;

&lt;p&gt;한 팀이 연구부터 운영까지 책임지는 구조는 이 두 가지 손실을 막는다. &lt;a href="https://treesoop.com/services/agentic-ai" rel="noopener noreferrer"&gt;아젠틱 AI 시스템 구축&lt;/a&gt;처럼 여러 에이전트가 협업하는 복잡한 구조일수록 이 연속성이 더 중요해진다. 연구자가 설계한 에이전트 간 인터페이스를 개발자가 직접 구현해야 실제 병목이 어디서 발생하는지 알 수 있다.&lt;/p&gt;

&lt;p&gt;DORA(DevOps Research and Assessment) 메트릭에서 배포 빈도와 장애 복구 시간이 팀 구조와 강하게 연관된다는 점은 잘 알려져 있다. AI 시스템도 다르지 않다. 연구-개발-운영 사이클이 한 팀 안에 있을수록 피드백 루프가 짧아진다.&lt;/p&gt;




&lt;h2&gt;
  
  
  공개 검증 가능한 기술 자산 — 오픈소스와 팀 구성
&lt;/h2&gt;

&lt;p&gt;나무숲은 GitHub에 오픈소스 자산 ★120+ 를 공개하고 있다. 이건 자랑이 아니라 검증 가능한 사실이다. 계약 전에 직접 확인할 수 있다.&lt;/p&gt;

&lt;p&gt;팀 구성도 확인 가능하다. POSTECH·KAIST·서울대 출신 개발 인력이 팀 안에 있다는 건, 논문 수준의 기술을 제품으로 옮기는 능력이 내부에 있다는 의미다. 이를테면 컴퓨터 비전 모델을 실제 제조 공정에 붙이거나, 자연어 처리 기술을 도면 부품 추출에 적용하는 작업이다.&lt;/p&gt;

&lt;p&gt;누적 41건 프로젝트(R&amp;amp;D 12건 + AX 10건 + 외주 19건)와 위시켓 평점 4.92는 공개 플랫폼에서 확인할 수 있다. 숫자 자체보다 중요한 건 R&amp;amp;D와 AX(AI 전환) 프로젝트가 외주 건수만큼 쌓여 있다는 점이다. 단순 개발 외주와 AI 연구개발을 같은 팀이 처리해온 이력이다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://treesoop.com/cases/emotion-ai" rel="noopener noreferrer"&gt;감정 AI 구현 사례&lt;/a&gt;나 &lt;a href="https://treesoop.com/cases/ai-startup-bot" rel="noopener noreferrer"&gt;AI 스타트업 챗봇 사례&lt;/a&gt;에서 실제 구현 방식을 확인할 수 있다. 포트폴리오를 볼 때 결과 수치보다 아키텍처 선택과 트레이드오프 설명이 있는지를 먼저 본다.&lt;/p&gt;




&lt;h2&gt;
  
  
  계약 전 반드시 던져야 할 기술 질문 5가지
&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;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;/td&gt;
&lt;td&gt;구체적 모델명 + 선택 근거 + 대안 비교&lt;/td&gt;
&lt;td&gt;"최신 AI 기술을 활용합니다"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;운영 환경에서 레이턴시와 비용을 어떻게 모니터링할 계획인가?&lt;/td&gt;
&lt;td&gt;구체적 툴(Prometheus, CloudWatch 등) + 임계값 설정 방식&lt;/td&gt;
&lt;td&gt;"운영 후 확인하겠습니다"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&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;4&lt;/td&gt;
&lt;td&gt;이 팀에서 R&amp;amp;D와 개발을 모두 담당한 경험이 있는가?&lt;/td&gt;
&lt;td&gt;구체적 프로젝트 예시 + 담당자 이름&lt;/td&gt;
&lt;td&gt;포트폴리오 PDF만 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;장애 발생 시 대응 프로세스와 SLA는 어떻게 정의하는가?&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;다섯 번째 질문은 기술 역량과 직접 관계없어 보이지만, AI 시스템은 결정론적이지 않다. 같은 입력에 다른 출력이 나올 수 있고, 모델 드리프트가 서서히 발생한다. 이걸 운영 관점에서 설명하는 팀이 실제로 AI를 운영해본 팀이다.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  포트폴리오만 봐도 기술 수준을 판단할 수 있을까?
&lt;/h3&gt;

&lt;p&gt;포트폴리오만으로는 부족하다. 결과 화면과 기능 목록은 외부 라이브러리 조합으로도 만들 수 있다. 아키텍처 설계 이유, 모델 선택 근거, 실패 사례와 대응 방식을 직접 물어봐야 실제 구현 역량이 드러난다.&lt;/p&gt;

&lt;h3&gt;
  
  
  오픈소스 기여 이력이 없는 팀은 배제해야 할까?
&lt;/h3&gt;

&lt;p&gt;반드시 그렇지는 않다. 단, 공개 검증 수단이 없다면 다른 방식으로 확인해야 한다. 기술 미팅에서 실시간 아키텍처 설명, 코드 리뷰 샘플 요청, 레퍼런스 체크가 대안이 된다.&lt;/p&gt;

&lt;h3&gt;
  
  
  R&amp;amp;D 역량과 제품 개발 역량은 다른 팀에서 받으면 안 되나?
&lt;/h3&gt;

&lt;p&gt;불가능하진 않다. 다만 두 팀 사이의 인터페이스 설계와 맥락 전달을 누가 책임지는지를 명확히 해야 한다. 이 연결이 흐릿한 구조에서 AI 프로젝트 지연이 가장 자주 발생한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI 개발 외주와 일반 소프트웨어 외주의 검증 방식이 다른가?
&lt;/h3&gt;

&lt;p&gt;다르다. 일반 소프트웨어는 스펙 충족 여부로 검증하지만, AI 시스템은 성능 분포, 엣지 케이스 처리, 모델 드리프트 대응까지 확인해야 한다. 계약서에 이 항목들을 어떻게 정의했는지가 핵심이다.&lt;/p&gt;

&lt;h3&gt;
  
  
  착수 속도와 기술 깊이는 트레이드오프인가?
&lt;/h3&gt;

&lt;p&gt;팀 구조에 달려 있다. 전원이 같은 AI 개발 환경을 표준으로 사용하는 팀이라면 착수를 빠르게 시작하면서도 기술 깊이를 유지할 수 있다. 이 두 가지가 트레이드오프로 느껴진다면, 그 팀은 AI 개발 환경이 아직 통일되지 않은 곳일 가능성이 높다.&lt;/p&gt;




&lt;p&gt;기술 담당자로서 AI 개발 외주를 고를 때 가장 피해야 할 건 "됩니다"라는 답변에 안심하는 것이다. 구체적인 트레이드오프를 설명하고, 공개 검증 가능한 기술 자산이 있으며, 연구부터 운영까지 같은 팀이 책임지는 구조인지를 확인하는 것—이 세 기준이 계약 후 후회를 막는 실질적인 필터다. 구체적인 기술 검증을 원한다면 &lt;a href="https://treesoop.com/services/ax-consulting" rel="noopener noreferrer"&gt;나무숲 기술 상담&lt;/a&gt;에서 직접 확인할 수 있다.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;더 보기: &lt;a href="https://treesoop.com?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;treesoop.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>management</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
