본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 30. 06:25

Azure와 NVIDIA를 사용하여 첫 멀티 에이전트 AI 시스템을 구축하며 배운 것들

요약

Azure AI Foundry와 NVIDIA NIM을 활용해 멀티 에이전트 시스템을 구축하며 겪은 실무적 시행착오를 다룹니다. 비용 산정 방식, 캐싱 전략, OpenTelemetry를 이용한 모니터링 설정 및 의존성 관리의 중요성을 설명합니다.

핵심 포인트

  • 비용은 토큰 수가 아닌 작업 단위와 모델 성능에 따라 결정됨
  • 자연어 워크로드에는 문구 일치 캐시보다 의미론적 캐싱이 필수적임
  • Azure Monitor 사용 시 HTTPX 클라이언트 계측을 명시적으로 설정해야 함
  • OpenTelemetry 라이브러리 간 버전 충돌을 방지하기 위해 버전을 고정해야 함
  • 스크립트 종료 시 OpenTelemetry 트레이스 누락을 막기 위해 플러시 처리가 필요함

최근 저는 Azure AI Foundry와 NVIDIA NIM을 사용하여 멀티 에이전트 (multi-agent) 고객 지원 시스템을 구축했습니다. 이런 작업을 해보는 것은 처음이었습니다. 저는 어떤 일이 일어날지에 대해 네 가지 예측을 미리 했습니다. 그중 세 가지는 틀렸습니다.
이곳에 제가 실제로 배운 것들이 있습니다.

1. "토큰 (Tokens)"은 비용의 단위가 아니다

그것은 작업의 단위입니다. 작업 단위당 가격은 어떤 모델이 작업을 수행하느냐에 따라 5~10배까지 차이가 납니다. 저는 9B 소형 모델과 49B 대형 모델의 비용이 동일한 것처럼 전체 토큰 수를 추적하고 있었습니다. 하지만 그렇지 않습니다. 최적화된 버전에서는 전체 토큰 수가 증가했습니다. 달러 기준 비용은 아마도 감소했을 것입니다. 저는 내내 잘못된 것을 측정하고 있었습니다.

2. 자연어 트래픽에 대한 문구 일치 해시 캐시 (verbatim hash cache)는 쿼리의 약 0%를 차단한다

저는 캐시 차단율이 25-40%가 될 것이라고 예측했습니다. 실제 수치는 0%였습니다. 제 테스트 세트의 모든 쿼리는 고유한 문자열이었기 때문에, 해시 기반 캐시가 작동할 기회가 단 한 번도 없었습니다. 문구 일치 캐시 (verbatim cache)는 의미론적 캐시 (semantic cache)의 단순화된 버전이 아닙니다. 그것은 완전히 다른 것입니다. 워크로드(workload)가 자연어라면, 나중에 업그레이드하는 것이 아니라 첫날부터 의미론적 유사성 캐싱 (semantic similarity caching)을 구축하십시오.

3. configure_azure_monitor()는 기본적으로 OpenAI SDK 호출을 캡처하지 않는다

opentelemetry-instrumentation-httpx를 명시적으로 설치하고 초기화해야 합니다:

pip install opentelemetry-instrumentation-httpx==0.61b0

from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
HTTPXClientInstrumentor().instrument()

이것이 없으면, App Insights 로그에 customMetric 및 performanceCounter 항목(CPU, 메모리)은 표시되지만, 에이전트가 실제로 무엇을 했는지에 대한 정보는 아무것도 나타나지 않습니다.

4. OpenTelemetry 버전을 고정하지 않으면 모든 것이 망가진다

버전 고정 없이 opentelemetry-instrumentation-httpx를 설치하면 opentelemetry-api 1.42.1이 함께 설치됩니다. 하지만 azure-monitor-opentelemetry-exporter는 opentelemetry-api==1.40을 필요로 합니다. 이 충돌은 문제가 발생하기 전까지는 조용히 진행됩니다. 모든 것을 0.61b0 / 1.40.0 라인으로 고정하십시오:

pip install \
"opentelemetry-api==1.40.0" \
"opentelemetry-instrumentation==0.61b0" \
"opentelemetry-instrumentation-httpx==0.61b0" \
"opentelemetry-semantic-conventions==0.61b0" \
"opentelemetry-util-http==0.61b0"

그 다음 pip check를 실행하여 깨진 요구 사항이 없는지 확인하십시오.

5. 실행 시간이 짧은 Python 스크립트가 OTel의 배치 익스포터(batch exporter)가 작동하기 전에 종료됨

OpenTelemetry는 트레이스(traces)를 배치(batch)로 모아서 몇 초마다 백그라운드에서 전송합니다. 만약 타이머가 작동하기 전에 스크립트가 종료되면, 트레이스는 조용히 누락됩니다. 지연되는 것이 아니라, 사라지는 것입니다. atexit 플러시(flush)를 추가하십시오:

import atexit
from opentelemetry import trace

def _flush():
provider = trace.get_tracer_provider()
if hasattr(provider, "force_flush"):
provider.force_flush()

atexit.register(_flush)

이렇게 하면 프로세스가 종료되기 전에 버퍼링된 트레이스가 반드시 전송됨을 보장할 수 있습니다.

6. Nemotron Nano와 Super는 출력을 content가 아닌 reasoning_content에 넣음

짧은 프롬프트의 경우, 두 모델 모두 토큰 예산을 내부 추론(internal reasoning)에 소비하며 content 필드를 생성하지 않습니다. 이 경우 결과는 None으로 반환됩니다. 그러면 msg.content.strip() 호출 시 AttributeError가 발생하며 충돌합니다.

항상 다음과 같이 텍스트를 추출하십시오:

text = (
msg.content
or getattr(msg, "reasoning_content", None)
or "(no response)"
)

이는 분류기(classifiers), 답변 함수(answer functions), 테스트 스크립트 등 모델 출력을 읽는 모든 곳에 적용됩니다.

7. 카탈로그의 NVIDIA 모델 이름은 API 모델 문자열과 다름

nvidia/nemotron-nano-9b-v2는 404 에러를 반환합니다. 실제 API 문자열에는 이중 접두사가 붙습니다:

nvidia/nvidia-nemotron-nano-9b-v2

build.nvidia.com에 접속하여 모델 카드(model card)를 열고, Python 탭을 클릭한 뒤 model= 값을 직접 복사하십시오. 카탈로그 이름을 보고 추측하지 마십시오.

8. 분류 작업을 수행하는 추론 모델(reasoning models)에는 max_tokens=10이 작동하지 않음

분류기 호출 시 한 단어의 레이블이 반환될 것을 기대하며 max_tokens=10으로 설정했습니다. Nemotron Nano는 10개의 토큰을 모두 추론 과정 (reasoning trace)에 사용하느라 레이블을 전혀 생성하지 못했습니다. 결과값(content)은 None으로 돌아왔습니다. 단순한 분류 작업이라 할지라도 추론 모델 (reasoning model)을 호출할 때는 최소 max_tokens=100으로 설정하십시오.

9. 라우팅 결정 (Routing decisions)에는 별도의 로그 라인이 필요함

라우터 (router)를 구축하여 81개의 쿼리를 실행하고 전체 벤치마크 결과를 얻었지만, 여전히 무엇이 어디로 라우팅되었는지 확신을 가지고 말씀드릴 수 없습니다. 제 벤치마크의 카테고리별 테이블은 라우터가 실제로 결정한 내용이 아니라 정답 레이블 (ground-truth label)을 기준으로 그룹화되어 있었습니다. 이 둘은 서로 다른 것입니다.
모든 쿼리에 대해 라우팅 결정을 다른 모든 것과 분리하여 명시적으로 로그에 남기십시오.

10. 순차적 벤치마크 (sequential benchmark)에서는 우아한 성능 저하 (Graceful degradation)를 테스트할 수 없음

추론 모델의 이동 평균 p95 지연 시간 (rolling p95 latency)이 4000ms를 초과할 때 작동하는 다운시프트 (downshift) 메커니즘을 구축했습니다. 이 메커니즘이 활성화되려면 윈도우 내에 20개의 샘플이 필요합니다. 하지만 제 전체 평가 세트(eval set)에는 12개의 추론 쿼리만 있었습니다. 즉, 쿼리를 단 하나도 실행하기 전부터 이 메커니즘은 절대 작동하지 않을 것이 보장되어 있었습니다.
포화 상태 (saturation) 동작을 테스트하려면 순차적인 단일 패스 벤치마크가 아니라, 훨씬 더 큰 데이터셋이나 전용 동시 부하 테스트 (concurrent load test)가 필요합니다.

11. Homebrew Python 3.12는 일부 macOS 버전에서 libexpat 충돌이 발생함

python3.12 -m venv .venv 실행 시 다음과 같은 오류가 발생합니다:

ImportError: Symbol not found: _XML_SetAllocTrackerActivationThreshold

Homebrew의 Python은 macOS에 포함된 것보다 더 최신 버전의 libexpat를 대상으로 컴파일되었습니다. 해결 방법은 pyenv를 사용하는 것입니다:

brew install pyenv
pyenv install 3.12.13
pyenv local 3.12.13
python -m venv .venv

그 이후에는 모든 것이 정상적으로 작동합니다.

12. 운영 계층 (operational layer)은 모델 계층 (model layer)보다 어렵다

이 목록에 있는 모든 실수는 제가 잘못 구축하거나, 측정하거나, 설정한 것들입니다. 그 중 어느 것도 Azure AI Foundry나 NVIDIA NIM의 문제는 아닙니다. 모델은 잘 작동했습니다. 플랫폼도 잘 작동했습니다. 격차는 모두 그 주변의 시스템을 어떻게 계측(instrumented)하고, 테스트하고, 측정하느냐에 있었습니다.

그것이 아마도 제가 배운 가장 유용한 점일 것입니다.

전체 벤치마크 결과 및 상세 내용은 제 블로그에서 확인하실 수 있습니다:
https://sachin.magonus.com/2026/06/17/multi-agent-poc-benchmark-foundry-nvdia-nim-results/

프레임워크 포스트 (실행 전의 아키텍처 및 예측 내용):
https://sachin.magonus.com/2026/01/16/multi-agent-framework-foundry-nvidia/

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0