Loading the catalog…
Loading the catalog…
Langfuse 공부 1 — Docker와 Ollama로 로컬 실습 환경 만들기 이 글에서 다룰 주제 Langfuse와 로컬 LLM의 역할을 나누고, 실제 요청과 관측 데이터가 이동하는 경로를 이해한다. macOS에는 Ollama와 Python을, Docker에는 Langfuse와 저장소를 준비한다. 한 번의 LLM 호출을 trace로 남기고, 저장된 입력·출력·토큰 사용량을 확인한다. 주요 단어 · Langfuse · Ollama · Trace · Span · Generation · Docker Compose LLM이 답을 돌려줬다는 사실만으로는 애플리케이션의 동작을 충분히 설명하기 어렵다. 어떤 프롬프트가 전달됐는지, 응답에 얼마나 걸렸는지, 여러 단계 중 어디서 문제가 생겼는지를 함께 남겨야 한다. 이번 공부는 외부 유료 LLM API 없이 시작한다. Ollama가 로컬 모델을 실행하고, Python 앱이 그 모델을 호출하며, Langfuse는 그 실행을 관측한다. 첫 글의 목표는 대규모 서비스를 만드는 것이 아니라, 이후 프롬프트 비교와 평가를 반복할 수 있는 작은 실습 환경을 준비하는 것이다. 이전에 정리한 AIOps 에이전트 운영과 Langfuse 에서 운영 관점을 살펴봤다면, 이번 글에서는 직접 데이터를 남기는 출발점을 만든다. 1. 도구의 역할 Langfuse 는 LLM 애플리케이션의 실행, 입력·출력, 모델 호출 등의 관측 데이터를 수집하고 조회하는 도구다. 이 실습에서 답변을 생성하는 모델 서버 역할은 Ollama가 맡는다. Ollama 는 로컬 모델을 내려받아 실행하고 API로 호출할 수 있게 하는 도구다. 이번 모델은 qwen3:8b 이며, Python은 Ollama의 OpenAI 호환 API를 호출한다. Langfuse를 설치했다고 Python 앱의 모든 호출이 저절로 기록되지는 않는다. 앱 코드에 SDK를 붙여 어떤 실행을 기록할지 정해야 한다. 예제는 LLM 호출 전후를 직접 감싸는 방식으로 작성했다. 자동 계측보다 코드가 조금 길지만, 부모 실행과 모델 호출이 어떻게 연결되는지 처음 공부할 때 확인하기 쉽다. 이 글에서 먼저 익힐 용어는 세 가지다. 용어 뜻 이번 예제 Trace 하나의 요청에 속하는 실행 전체 질문 하나를 처리한 실행 Span 실행 안의 일반 작업 구간 local-ollama-study Generation 모델 호출을 표현하는 관측 구간 ollama-chat Generation에는 일반 작업 정보에 더해 모델명과 토큰 사용량 같은 LLM 관련 정보를 기록한다. 이번 trace는 루트 span 하나와 그 아래 generation 하나로 시작한다. 개념과 API는 Langfuse 계측 문서 를 기준으로 확인했다. 2. 전체 구성 전체 구성도. 파란 화살표는 LLM·UI 요청, 초록 화살표는 관측 데이터 전송이다. 보라색 선은 Web/Worker가 사용하는 공통 저장소의 연결 의존성이며, 저장소 사이의 전달 순서를 뜻하지 않는다. Langfuse는 web 컨테이너 하나로 끝나지 않는다. 이번 구성은 web, worker, PostgreSQL, ClickHouse, Redis, MinIO의 여섯 서비스를 사용한다. 구성요소 이 실습의 역할 Langfuse web 브라우저 UI, API, SDK 데이터 수신 Langfuse worker 들어온 이벤트의 비동기 처리 PostgreSQL 사용자·조직·프로젝트 등 트랜잭션 데이터 ClickHouse trace와 observation 등 분석용 관측 데이터 Redis 작업 큐와 캐시 MinIO S3 호환 저장소. 수집 원본 이벤트와 미디어 저장 수집 API는 원본 이벤트를 객체 저장소에 두고 처리를 큐에 넣는다. worker가 비동기로 처리한 뒤 관측 데이터를 ClickHouse에 저장하므로, SDK 호출이 끝나는 순간과 UI에서 검색되는 순간 사이에는 차이가 날 수 있다. 저장소별 역할과 이 흐름은 공식 아키텍처 설명 에 근거한다. 2.1 네이티브 Ollama Apple Silicon에서 Ollama는 Metal을 통한 GPU 가속을 지원한다. 이번에는 이 경로를 사용하도록 Ollama를 macOS에 직접 설치하고, Langfuse 쪽 서비스만 Docker로 실행했다. Python도 호스트에서 실행하므로 모델 주소는 http://127.0.0.1:11434/v1 이다. Ollama 하드웨어 지원 여기서 localhost 는 호출하는 프로세스가 실행 중인 환경 을 가리킨다. 지금 Python 코드를 컨테이너로 옮긴다면 같은 127.0.0.1 주소는 호스트의 Ollama가 아니라 그 컨테이너 자신을 가리키므로 주소 설정을 다시 해야 한다. 2.2 실습 환경 2026년 10월 4일에 준비한 환경은 다음과 같다. 항목 사용 환경 호스트 macOS 26.6.2 · Apple M5 Pro · 메모리 64GB Docker Engine / Compose 29.7.2 / 5.4.0 Docker에 할당된 메모리 약 7.75GiB Langfuse web / worker 4.50.0 Ollama Homebrew 패키지 0.35.1_1 Python 3.14.8 Python 주요 패키지 langfuse 4.16.0 · openai 3.24.0 · httpx 0.28.1 · python-dotenv 1.2.4 모델 qwen3:8b 서버와 SDK의 버전은 서로 다른 제품 버전이다. 둘 다 이름에 Langfuse가 들어간다고 같은 숫자를 맞추는 것은 아니다. 이번 앱은 서버 v4의 observation 조회 API를 사용한다. 버전 업그레이드 시에는 호환성 문서 를 함께 확인한다. qwen3:8b 의 모델 페이지는 다운로드 크기 약 5.2GB와 Q4_K_M 양자화를 표시한다. 이 숫자는 실행에 필요한 전체 메모리 크기가 아니다. 모델 가중치 외에 컨텍스트와 실행 버퍼 등도 메모리를 사용한다. Ollama 모델 정보 Docker 메모리 제한은 이번 단일 요청 실습을 위한 설정이다. 운영 환경의 권장 사양이나 처리량을 검증한 결과로 해석하면 안 된다. Compose 환경 자체도 고가용성과 백업을 갖춘 운영 구성이 아니다. 공식 Compose 설치 문서 3. Ollama 설치 기존 Homebrew 환경에 Ollama를 설치하고 macOS 서비스로 실행했다. brew install ollama brew services start ollama ollama --version curl http://127.0.0.1:11434/api/version ollama pull qwen3:8b ollama list 첫 다운로드에는 인터넷 연결과 모델 파일을 저장할 공간이 필요하다. 내려받은 모델의 로컬 추론과 모델 다운로드는 서로 다른 작업이다. 이번 실습은 로컬 추론에 집중하기 위해 Ollama 설정 파일 ~/.ollama/server.json 에 아래 값을 적용했다. 이미 다른 설정이 있다면 파일 전체를 덮어쓰지 않고 이 항목을 합쳐야 한다. 이번에는 첫 서비스 시작 전에 설정했다. 실행 중인 서비스의 설정을 바꿨다면 brew services restart ollama 로 다시 시작한다. { "disable_ollama_cloud": true } 모델이 로딩된 동안 ollama ps 를 실행하면 메모리에 올라온 모델과 처리 장치를 확인할 수 있다. 공식 문서에서 100% GPU 는 모델이 전부 GPU 메모리에 올라온 상태를 뜻한다. 이번 실행의 ollama ps 에서도 100% GPU 가 표시됐다. 출력의 모델 메모리 크기는 8.8GB, 컨텍스트 설정은 40,960토큰이었다. 이는 실제로 질문에 40,960토큰을 사용했다는 뜻은 아니다. Ollama FAQ 4. Langfuse 실행 4.1 Compose 설정 공식 저장소의 Compose 파일을 출발점으로 사용했다. 실습 파일은 docs/langfuse-study-2026-10-04/lab 에 모았다. lab/ ├── compose.yaml ├── upstream-compose.yaml ├── init_env.py ├── .env # 비밀값, 공개하지 않음 ├── .venv/ └── app/ ├── requirements.txt ├── config.py ├── trace_smoke.py └── verify_trace.py 주요 변경은 브라우저 주소, 비밀값, 저장소 노출 범위다. 설정 이번 값과 이유 web 포트 127.0.0.1:3300:3000 . 호스트 3300에서 컨테이너 3000으로 연결 NEXTAUTH_URL http://localhost:3300 . 실제 브라우저 접속 주소와 일치 MinIO 호스트 포트 127.0.0.1:9190:9000 . 브라우저 미디어 경로에 사용 내부 MinIO 주소 http://minio:9000 . Docker 서비스 이름으로 접근 DB·Redis·worker 포트 호스트에 따로 공개하지 않음 CLICKHOUSE_CLUSTER_ENABLED false . 단일 ClickHouse 컨테이너 사용 서버 익명 사용 통계 TELEMETRY_ENABLED=false 영속 데이터 Compose의 named volume에 보관 TELEMETRY_ENABLED=false 는 Langfuse 서버의 사용 통계 설정이다. 학습 앱에서 보내는 trace 계측을 끄는 의미로 사용한 것이 아니다. 포트를 바꿀 때 ports 만 바꾸면 브라우저 주소와 인증·미디어 URL이 어긋날 수 있다. NEXTAUTH_URL 과 미디어 업로드 URL도 함께 맞춰야 한다. 공식 포트 변경 안내 4.2 비밀값과 초기 프로젝트 init_env.py 는 DB 비밀번호, 암호화 키, API 키, 로컬 사용자 비밀번호를 무작위로 생성해 .env 에 저장한다. 파일 권한은 소유자만 읽고 쓸 수 있는 600 으로 설정했다. 실제 키와 비밀번호는 글과 스크린샷에 싣지 않는다. 초기 조직은 Local Study , 프로젝트는 Langfuse Local Lab 이다. LANGFUSE_INIT_* 환경변수를 이용해 처음 시작할 때 사용자·조직·프로젝트를 만든다. 이는 이 실습이 생성한 계정이며, Langfuse가 제공하는 공통 기본 관리자 비밀번호가 아니다. Headless initialization 다음은 이번에 생성한 로컬 실습 자료를 사용하는 명령이다. 저장소 루트에서 LAB_DIR 를 정한 뒤 해당 프로젝트와 Compose 파일을 명시해 실행한다. LAB_DIR="$PWD/docs/langfuse-study-2026-10-04/lab" cd "$LAB_DIR" /opt/homebrew/bin/python3.14 init_env.py docker compose -p langfuse-study -f "$LAB_DIR/compose.yaml" up -d docker compose -p langfuse-study -f "$LAB_DIR/compose.yaml" ps 컨테이너가 시작됐다는 사실과 서비스가 준비됐다는 사실은 다르다. 첫 실행에는 DB migration과 초기화가 필요하다. ps 에서 의존 저장소의 상태를 확인하고, 문제가 생기면 해당 서비스 로그를 읽는다. docker compose -p langfuse-study -f "$LAB_DIR/compose.yaml" \ logs --tail=100 langfuse-web langfuse-worker curl http://localhost:3300/api/public/health 브라우저의 접속 주소는 http://localhost:3300 이다. DB 비밀번호나 SDK secret key를 로그인 비밀번호로 넣지 않고, 생성된 로컬 사용자 계정을 사용한다. 4.3 공식 파일로 시작하기 앞의 init_env.py , app/trace_smoke.py 등은 이번에 만든 로컬 실습 자료 다. 내 작업 폴더가 없는 독자는 별도의 새 폴더에서 공식 파일로 시작할 수 있다. 아래 경로는 같은 버전의 공식 Compose를 가져오는 시작 절차이며, 이번 실측은 앞에서 설명한 수정본에서 수행했다. mkdir langfuse-reader-lab cd langfuse-reader-lab curl -fL \ https://raw.githubusercontent.com/langfuse/langfuse/v4.50.0/docker-compose.yml \ -o compose.yaml 공식 파일에서 # CHANGEME 로 표시된 비밀값을 바꾸고 다음 항목을 맞춘다. 임의의 예제 비밀번호를 그대로 사용하는 대신 openssl rand -hex 32 등으로 각 값을 생성한다. 같은 저장소에 연결하는 항목에는 동일한 비밀번호를 넣어야 한다. 공식 파일의 위치 변경할 값 web / worker의 이미지 각각 docker.langfuse.com/langfuse/langfuse:4.50.0 , docker.langfuse.com/langfuse/langfuse-worker:4.50.0 web의 ports 127.0.0.1:3300:3000 worker 공통 NEXTAUTH_URL http://localhost:3300 MinIO의 ports 127.0.0.1:9190:9000 web의 미디어 외부 endpoint http://localhost:9190 worker의 미디어 endpoint 내부 주소 http://minio:9000 유지 MinIO·S3 비밀번호 MINIO_ROOT_PASSWORD 와 모든 *_SECRET_ACCESS_KEY 에 같은 값 PostgreSQL 비밀번호 POSTGRES_PASSWORD 와 DATABASE_URL 에 같은 값 ClickHouse 비밀번호 서버와 web/worker의 CLICKHOUSE_PASSWORD 에 같은 값 Redis 비밀번호 Redis 명령의 --requirepass 와 web/worker REDIS_AUTH 에 같은 값 Langfuse 암호화·세션 SALT , NEXTAUTH_SECRET 은 각각 생성, ENCRYPTION_KEY 는 32바이트의 64자리 hex 이 표는 .env 만으로 모든 변경이 적용된다는 뜻이 아니다. 이미지와 ports , web의 미디어 endpoint처럼 파일에 적힌 위치를 직접 수정하는 항목도 있다. Compose 설정이 유효한지 확인한 뒤 실행한다. docker compose -p langfuse-reader-lab -f compose.yaml config --quiet docker compose -p langfuse-reader-lab -f compose.yaml pull docker compose -p langfuse-reader-lab -f compose.yaml up -d docker compose -p langfuse-reader-lab -f compose.yaml ps 처음 접속하면 사용자·조직·프로젝트를 만들고 프로젝트의 API 키를 발급한다. 그 키를 Python 예제용 .env 의 LANGFUSE_PUBLIC_KEY , LANGFUSE_SECRET_KEY 에 넣는다. 뒤의 짧은 Python 예제를 first_trace.py 로 저장하면 직접 만든 app/ 폴더 없이도 같은 계측 구조를 확인할 수 있다. 공식 Compose의 의존 이미지 일부는 범위 태그를 쓴다. 재현할 버전을 정확히 보존하려면 내려받은 이미지의 digest도 기록·고정해야 한다. 실제 로컬 구성은 여섯 이미지 모두 내려받은 arm64 digest로 고정했고, 이미지 식별자를 lab/images.lock.json 에 보관했다. 범위 태그 redis:7 이나 latest 만 기록한 상태와 구분한다. 5. 첫 관측 데이터 빨간 윤곽은 현재 설명하는 계측 영역이다. 모델 요청은 위쪽 Python→Ollama 경로로 나가고, 그 호출을 설명하는 관측 데이터는 아래쪽 Langfuse web으로 전송된다. 5.1 Python 환경 별도 가상환경에 고정한 패키지를 설치한다. 아래 명령은 앞에서 준비한 lab 폴더 기준이다. 이 Mac의 기본 python3 는 3.9였으므로 이번 실습에서는 Homebrew Python 3.14 경로를 명시했다. 다른 환경에서는 Python 3.10 이상의 실행 경로로 바꾼다. /opt/homebrew/bin/python3.14 -m venv .venv .venv/bin/python -m pip install -r app/requirements.txt .venv/bin/python app/trace_smoke.py .venv/bin/python app/verify_trace.py trace_smoke.py 는 다음 질문을 로컬 모델에 전달한다. Langfuse에서 trace, span, generation의 차이를 한국어로 각각 한 문장씩 설명해줘. OpenAI 라는 Python 클라이언트 이름을 사용하지만, 이번 base_url 은 Ollama의 로컬 주소다. 클라이언트 이름과 실제 요청 목적지를 구분해야 한다. api_key="ollama" 는 OpenAI 호환 클라이언트에 넣는 더미 문자열이며, 유료 OpenAI API 키를 발급할 필요가 없다. Ollama OpenAI 호환 API 5.2 계측 코드 전체 스크립트에서 핵심은 모델 호출을 generation으로 감싼 부분이다. 아래는 별도 파일 first_trace.py 로 저장해 실행할 수 있는 짧은 예제다. 현재 폴더의 .env 에 LANGFUSE_PUBLIC_KEY , LANGFUSE_SECRET_KEY 를 준비한다. 키는 프로젝트 설정에서 발급하며, 이 글의 로컬 프로젝트에는 초기화 때 생성한 키가 들어 있다. import os from dotenv import load_dotenv from langfuse import Langfuse from openai import OpenAI load_dotenv(".env") langfuse = Langfuse( public_key=os.environ["LANGFUSE_PUBLIC_KEY"], secret_key=os.environ["LANGFUSE_SECRET_KEY"], base_url="http://localhost:3300", ) assert langfuse.auth_check(), "Langfuse 연결과 프로젝트 키를 확인하세요" prompt = "Langfuse의 trace, span, generation을 각각 한 문장으로 설명해줘." messages = [{"role": "user", "content": prompt}] ollama = OpenAI( base_url="http://127.0.0.1:11434/v1", api_key="ollama", ) with langfuse.start_as_current_observation( as_type="span", name="local-ollama-study", input={"question": prompt}, ) as root: trace_id = langfuse.get_current_trace_id() with langfuse.start_as_current_observation( as_type="generation", name="ollama-chat", model="qwen3:8b", input=messages, ) as generation: response = ollama.chat.completions.create( model="qwen3:8b", messages=messages, temperature=0.2, max_tokens=512, reasoning_effort="none", ) answer = response.choices[0].message.content generation.update( output=answer, usage_details={ "input": response.usage.prompt_tokens, "output": response.usage.completion_tokens, "total": response.usage.total_tokens, }, ) root.update(output={"answer": answer}) langfuse.flush() print(answer) print(langfuse.get_trace_url(trace_id=trace_id)) langfuse.shutdown() ollama.close() 짧은 예제만 실행할 때는 다음처럼 의존성을 설치하고 실행한다. 이 예제는 API 재조회 검증을 포함하지 않으므로 출력된 trace 주소에서도 저장 결과를 확인한다. /opt/homeb
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
[Langfuse 1] Docker와 Ollama로 로컬 LLM 관측 환경 만들기. Langfuse 공부 1 — Docker와 Ollama로 로컬 실습 환경 만들기 이 글에서 다룰 주제 Langfuse와 로컬 LLM의 역할을 나누고, 실제 요청과 관측 데이터가 이동하는 경로를 이해한다. macOS에는 Ollama와 Python을, Docker에는 Langfuse와 저장소를 준비한다. 한 번의 LLM 호출을 trace로 남기고, 저장된 입력·출력·토큰 사용량을 확인한다. 주요 단어 · Langfuse · Ollama · Trace · Span · Generation · Docker Compose LLM이 답을 돌려줬다는 사실만으로는 애플리케이션의 동작을 충분히 설명하기 어렵다. 어떤 프롬프트가 전달됐는지,…
Open source