Flue와 Grafana/Tempo를 사용하여 LLM 에이전트의 동작을 추적해보기
요약
LLM 에이전트의 복잡한 동작을 추적하고 개선하기 위해 '가시성(observability)' 확보가 중요합니다. 본 기사는 Flue라는 하네스 프레임워크를 사용하여 LLM 에이전트를 코드에서 호출하고, OpenTelemetry 출력을 통해 Tempo와 Grafana로 트레이스를 전송하여 실행 과정을 시각화하는 방법을 소개합니다.
핵심 포인트
- LLM 에이전트의 동작 가시성 확보가 핵심 과제입니다.
- Flue는 LLM을 외부 시스템에 연계하며 세밀한 제어가 가능합니다.
- OpenTelemetry를 통해 트레이스를 Tempo/Grafana로 전송하여 시각화할 수 있습니다.
- Node.js 환경 및 Cloudflare Workers 등 다양한 곳에서 구동이 가능합니다.
서론
소프트웨어 개선에 관한 원칙 중 하나에는 '추측하지 말고 측정하라(Don't guess, measure)'는 것이 있습니다. LLM 에이전트는 채팅으로 대화하며 사용하는 도구에서 업무 흐름의 일부로 통합되어 자동으로 움직이는 존재로 활약 영역을 넓히고 있으며, 그 개선 역시 당연히 이 원칙과 무관할 수 없게 되어가고 있습니다.
업무 흐름 작업의 일부를 LLM 에이전트에게 수행하게 할 경우, 출력 결과가 예상했던 것과 다를 때 모델 교체나 스킬 재검토 등의 수정 작업을 진행해야 합니다. 하지만 오늘날의 LLM 에이전트는 복잡하여, 작업 최종 출력의 이질감이 어디에서 기인하는지 쉽게 찾아내기는 어렵습니다. 즉, LLM 에이전트는 동작의 가시성(observability)에 과제가 있다고 할 수 있습니다.
본 기사에서는 LLM 에이전트를 통합한 정형 작업을 지속적으로 개선하는 데 중요하게 되는 '가시성'을 Flue, Grafana, Tempo를 사용하여 구현하는 예를 소개합니다.
Flue에 대하여
Flue는 TypeScript로 작성된 하네스 프레임워크입니다. 범용 LLM 에이전트를 Claude Code처럼 사람이 채팅으로 조작하는 것이 아니라, 코드에서 호출하여 동작시킬 수 있게 하는 것이 특징입니다. 본 기사에서는 '하네스(Harness)'를 LLM 에이전트가 수행하는 동작 중 LLM 이외의 시스템 연계 기능 등의 총칭으로 정의합니다.

Flue를 사용하면 Claude Code나 GitHub Copilot과 달리, LLM이 외부 시스템에 접근하는 부분을 사용자가 더 세밀하게 제어할 수 있습니다. 이를 통해 안전성을 확보하면서도 LLM의 유연성을 활용할 수 있게 됩니다. 또한, 동작을 세부적으로 기록할 수 있어 서두에서 언급된 가시성 문제에 대해서도 어느 정도 해결할 수 있습니다. Flue는 OpenTelemetry 출력을 지원하며, 후술하듯이 트레이스를 Tempo 같은 백엔드에 전송하여 Grafana로 표시함으로써 에이전트의 실행 과정을 시각화할 수 있습니다. 이러한 특징들 덕분에 Flue를 사용한 LLM 에이전트로 정형적인 처리를 수행했을 때도, 출력 결과에 이질감이 있을 경우에도 그 원인 특정 및 수정을 지속적으로 진행할 수 있게 됩니다.
또한, 본 기사에서는 다루지 않지만, Flue는 폭넓은 실행 환경에서 동작할 수 있다는 특징이 있어 로컬 터미널에만 머무르지 않고 Node.js 환경 전반에서 구동할 수 있습니다. 또한 Cloudflare Workers 등에도 배포하는 것이 가능하여, 바꿔 말하면 브라우저에서 쉽게 조작할 수 있는 시스템을 용이하게 만들 수 있습니다.
바로 설치해서 동작시켜 봅시다. 본 기사에서는 Node.js 24.2.0, Flue 2.1.1을 사용합니다.
mkdir flue-tempo-demo && cd flue-tempo-demo
mkdir workspace # 에이전트의 작업용 디렉토리
echo
또한, 아래와 같이 `run.ts`
을 생성하여 `node run.ts`
로 실행할 수도 있습니다:
import 'dotenv/config';
import { init } from '@flue/runtime';
import { start } from '@flue/runtime/node';
...
이후 설명드릴 OpenTelemetry에서의 출력 설정 등을 포함하여, `run.ts`
을 작성하는 것이 나중에 커스터마이징 할 때 편리합니다.
### Azure OpenAI의 LLM을 사용할 경우
LLM 에이전트의 LLM으로 Azure OpenAI의 LLM을 사용하는 경우에는 아래와 같이 `.env`
에 변수를 추가하고, 모델 프로바이더를 정의해야 합니다.
AZURE_OPENAI_API_KEY=[your_azure_openai_api_key]
AZURE_OPENAI_BASE_URL=https://[your_base_url_subdomain].openai.azure.com
모델 프로바이더의 정의는 `models.ts`
로 하여 아래와 같은 파일을 생성합니다.
import { setProvider } from '@flue/runtime';
import { createProvider } from '@earendil-works/pi-ai';
import { azureOpenAIResponsesApi } from '@earendil-works/pi-ai/api/azure-openai-responses.lazy';
...
예를 들어, Azure OpenAI의 모델로 "gpt-5.6-luna"가 배포되어 있다면, 위와 같은 파일을 생성한 후 `agent.ts`
의 모델 식별자를 `azure-openai-responses/gpt-5.6-luna`
으로 지정하여 사용할 수 있습니다.
참고로, CLI에서 `npx flue run ...`
으로 실행하는 경우에는 `agent.ts`
에서 이 `models.ts`
을 import 할 필요는 없으며, `.env`
에 변수 `AZURE_OPENAI_API_KEY`와 `AZURE_OPENAI_BASE_URL`
이 정의되어 있으면 자동으로 그 정보를 사용하여 실행 시점에 사용합니다. 반면, `node run.ts`
으로 실행하는 경우에는 `run.ts`
의 상단 근처에 `import './models.ts'`
을 추가해야 합니다.
## LLM 에이전트에 OpenTelemetry로 트레이스를 출력시키기
다음으로, LLM 에이전트의 동작을 시각화해 보겠습니다.
트레이스를 전송하기 위해 아래와 같이 `telemetry.ts`
을 생성합니다. OpenTelemetry의 전송지는 `.env`
에서 `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`
로 정의하고 있습니다.
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { instrument } from '@flue/runtime';
...
코드에서 직접 참조되지는 않지만, OpenTelemetry의 전송지(destination)는 환경 변수에서 가져옵니다. 따라서 `.env`에 다음 내용을 추가합니다. `OTEL_SERVICE_NAME`은 전송하는 서비스 이름(선택 사항)이며, `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`는 나중에 설정할 Tempo의 URL이 됩니다.
OTEL_SERVICE_NAME=flue-workspace-agent
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://127.0.0.1:4318/v1/traces
`run.ts`도 다음과 같이 수정합니다. 수정된 부분에 주석을 달았습니다.
import 'dotenv/config';
import { init } from '@flue/runtime';
import { start } from '@flue/runtime/node';
...
참고로, LLM의 thinking/reasoning을 기록하고 싶지 않은 경우, `telemetry.ts`를 아래 코드처럼 수정하면 이들을 OpenTelemetry 전송에서 제외할 수 있습니다.
## telemetry.ts 수정 버전 코드
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { instrument } from '@flue/runtime';
...
## Grafana와 Tempo에 대하여
Tempo는 수신한 트레이스를 저장하고 검색하는 백엔드이며, Grafana는 Tempo를 데이터 소스로 트레이스를 표시하는 시각화 도구입니다. 이번에는 이들의 상세 내용까지 깊이 다루지 않고, LLM 에이전트의 트레이스 시각화 도구로만 사용합니다.
### Compose 파일
Docker compose로 Grafana와 Tempo를 설정할 것입니다. 다음 내용을 `compose.yaml`로 저장합니다. 이 설정에서는 Grafana의 UI와 Tempo의 OTLP 수신 포트는 호스트 자신(127.0.0.1)에서만 접근 가능합니다. 동작 확인을 위해 익명 사용자에게 관리자 권한을 부여했으므로, 외부에 공개하지 않도록 주의해야 합니다. 서브넷 설정은 사용하는 환경에 맞춰 적절히 변경해 주세요.
services:
tempo:
image: grafana/tempo:3.0.0
...
### Grafana와 Tempo의 설정 파일
Grafana와 Tempo 연결 설정을 다음처럼 만듭니다.
stream_over_http_enabled: true
server:
http_listen_port: 3200
...
apiVersion: 1
datasources:
- name: Tempo
...
지금까지 만든 파일들을 정리하면 다음과 같습니다(`package.json` 등은 생략):
.
├── .env
├── agent.ts
...
## 실행해 보기
LLM 에이전트에게 작업 디렉토리 `./workspace` 안에서 `ls -la`를 실행하게 하고, 그 트레이스를 시각화해 보겠습니다.
먼저 Grafana와 Tempo를 Docker compose로 띄웁니다.
docker compose up -d
특별한 문제가 없다면 웹 브라우저에서 `127.0.0.1:3000`으로 Grafana에 접속할 수 있을 것입니다.
다음으로, `run.ts`를 실행하여 LLM 에이전트를 구동합니다. 이번의
해당 Trace ID를 선택하면 아래와 같은 화면이 나타나며, 실제 LLM으로의 입력과 응답, 그리고 실행된 명령어 및 그 경과 시간을 관찰할 수 있습니다.



확실히 LLM에서 tool call로 `ls -la`가 호출되고 있으며, 그 실행 시간도 알 수 있었습니다. 이 외에도 LLM으로의 입출력이나, tool call의 인자 등 다양한 정보를 이 화면에서 조사할 수 있습니다.
## 맺음말
아무리 블랙박스가 되기 쉬운 LLM 에이전트라도, 그 동작 개선에 있어서도 처음에 언급한 '추측하지 말고 측정하라'는 원칙은 변함이 없습니다. 본 기사가 LLM 에이전트를 개량하는 분들의 일조가 되기를 바랍니다.
## 참고 문헌
- Flue — The Open Agent Framework https://flueframework.com/
- Grafana Tempo OSS | Distributed tracing backend https://grafana.com/oss/tempo/
### 토론

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