Langfuse 프롬프트 GitHub 통합
Langfuse 프롬프트 GitHub 통합 (GitHub Integration for Langfuse Prompts)
Langfuse 프롬프트를 GitHub와 통합하는 방법은 두 가지가 있어요.
출처: 문서
본문
- GitHub Repository Dispatch — 프롬프트가 변경될 때 CI/CD 워크플로우를 트리거해요. 추가 인프라가 필요 없어요.
- GitHub로 프롬프트 동기화(Sync) — 프롬프트를 저장소의 특정 파일에 저장해요. 프롬프트 버전 변경을 듣고 저장소에 커밋하는 웹훅 서버가 필요해요.
GitHub Actions 트리거하기
repository_dispatch 이벤트를 사용해 Langfuse 프롬프트가 변경될 때 GitHub Actions 워크플로우를 트리거할 수 있어요.
1. GitHub 워크플로우 만들기
.github/workflows/langfuse-ci.yml:
name: Langfuse Prompt CI
on:
repository_dispatch:
types: [langfuse-prompt-update]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
echo "Testing prompt: ${{ github.event.client_payload.prompt.name }} v${{ github.event.client_payload.prompt.version }}"
# Add your test commands
# npm test
# python -m pytest
deploy:
needs: test
runs-on: ubuntu-latest
if: contains(github.event.client_payload.prompt.labels, 'production')
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: |
echo "Deploying ${{ github.event.client_payload.prompt.name }} v${{ github.event.client_payload.prompt.version }}"
# Your deployment commands
웹훅 데이터 접근: github.event.client_payload.*를 사용해 프롬프트 데이터에 접근할 수 있어요:
# Example: Access webhook data in your workflow
- name: Process prompt data
run: |
echo "Action: ${{ github.event.client_payload.action }}"
echo "Prompt: ${{ github.event.client_payload.prompt.name }}"
echo "Version: ${{ github.event.client_payload.prompt.version }}"
echo "Labels: ${{ github.event.client_payload.prompt.labels }}"
- name: Deploy only production prompts
if: contains(github.event.client_payload.prompt.labels, 'production')
run: echo "Deploying production prompt"
2. Actions용 GitHub 토큰 만들기
단계:
- GitHub Settings > Developer settings > Personal access tokens
- 새 토큰 생성(classic 또는 fine-grained)
- 범위 선택(아래 표 참고)
| 토큰 유형 | 필요한 권한 |
| Personal Access Token (classic) | repo 범위(공개 저장소) 또는 repo 범위(비공개 저장소) |
| Fine-grained PAT 또는 GitHub App | actions에 대한 읽기·쓰기 |
3. Langfuse에서 GitHub Action 구성하기
- Langfuse 프로젝트에서 Prompts > Automations로 이동.
- Create Automation 클릭.
- GitHub Repository Dispatch 선택.
- 자동화 구성:
- Dispatch URL: https://api.github.com/repos/{owner}/{repo}/dispatches (
{owner}와{repo}를 당신의 값으로 교체) - Event Type:
langfuse-prompt-update(GitHub 워크플로우의 타입과 일치해야 함) - GitHub Token: GitHub Personal Access Token 입력. 안전하게 저장됨.
- Dispatch URL: https://api.github.com/repos/{owner}/{repo}/dispatches (
4. GitHub Actions 통합 테스트
- Langfuse에서
production라벨로 프롬프트 업데이트 - GitHub Actions 탭에서 트리거된 워크플로우 확인
- test와 deploy 작업이 모두 성공적으로 실행되는지 확인
Langfuse 프롬프트를 저장소로 동기화하기
프롬프트 버전 웹훅을 사용해 Langfuse에서 GitHub로 프롬프트 변경을 자동으로 동기화해요. 이렇게 하면 프롬프트의 버전 관리를 활성화하고 CI/CD 워크플로우를 트리거할 수 있어요.
동기화 워크플로우 개요
Langfuse에서 새 프롬프트 버전을 저장할 때마다 GitHub 저장소에 자동으로 커밋돼요. 이 설정으로 프롬프트가 변경될 때 CI/CD 워크플로우도 트리거할 수 있어요.
동기화 사전 요구사항
- Langfuse 프로젝트: 프로젝트 소유자(Project Owner) 접근 권한이 있는 프롬프트 설정
- GitHub 저장소: 프롬프트를 저장할 공개 또는 비공개 저장소
- GitHub PAT: 최소 요구 권한을 가진 Personal Access Token(자세한 내용은 2단계 참고)
- Python 3.9+ (아래 예시 기준, 다른 언어도 가능) 및 FastAPI, Uvicorn, httpx, Pydantic
- 웹훅 서버용 공개 HTTPS 엔드포인트 (Render, Fly.io, Heroku 등)
1단계: Langfuse에서 프롬프트 웹훅 구성
- Langfuse 프로젝트에서 Prompts > Webhooks로 이동
- Create Webhook 클릭
- (선택) 이벤트 필터: 어떤 프롬프트 버전 이벤트에 웹훅을 받을지 필터링(기본: created, updated, deleted)
- 엔드포인트 URL 설정: https://
/webhook/prompt - 저장 후 Signing Secret 복사
참고: 엔드포인트는 2xx 상태 코드를 반환해야 해요. Langfuse는 실패한 웹훅을 지수 백오프로 재시도해요.
샘플 웹훅 페이로드
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2024-07-10T10:30:00Z",
"type": "prompt-version",
"action": "created",
"prompt": {
"id": "prompt_abc123",
"name": "movie-critic",
"version": 3,
"projectId": "xyz789",
"labels": ["production", "latest"],
"prompt": "As a {{criticLevel}} movie critic, rate {{movie}} out of 10.",
"type": "text",
"config": { "...": "..." },
"commitMessage": "Improved critic persona",
"tags": ["entertainment"],
"createdAt": "2024-07-10T10:30:00Z",
"updatedAt": "2024-07-10T10:30:00Z"
}
}
2단계: 동기화용 GitHub 저장소 및 토큰 준비
GitHub 자격 증명으로 .env 파일을 만드세요:
GITHUB_TOKEN=<your_github_pat_here>
GITHUB_REPO_OWNER=<github_username_or_org>
GITHUB_REPO_NAME=<repo_name>
# (Optional) GITHUB_FILE_PATH=langfuse_prompt.json
# (Optional) GITHUB_BRANCH=main
# (Optional) REQUIRED_LABEL=production
플레이스홀더를 실제 값으로 교체하세요. 서버는 기본적으로 main 브랜치의 langfuse_prompt.json에 프롬프트를 커밋해요. REQUIRED_LABEL이 설정되면 해당 라벨이 있는 프롬프트만 GitHub에 동기화돼요.
동기화용 GitHub PAT 권한
웹훅이 작동하려면 GitHub Personal Access Token에 최소 권한이 필요해요:
| 권한 유형 | 필요한 권한 |
| Required Permissions | Contents: Read and write, Metadata: Read-only |
| 레거시 토큰 범위 | 공개 저장소: public_repo 범위, 비공개 저장소: repo 범위 |
3단계: FastAPI 웹훅 서버 구현
이 FastAPI 서버로 main.py를 만드세요:
from typing import Any, Dict
from uuid import UUID
import json
import base64
import httpx
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
from fastapi import FastAPI, HTTPException, Body
class GitHubSettings(BaseSettings):
"""GitHub repository configuration."""
GITHUB_TOKEN: str
GITHUB_REPO_OWNER: str
GITHUB_REPO_NAME: str
GITHUB_FILE_PATH: str = "langfuse_prompt.json"
GITHUB_BRANCH: str = "main"
REQUIRED_LABEL: str = "" # Optional: only sync prompts with this label
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=True
)
config = GitHubSettings()
class LangfuseEvent(BaseModel):
"""Langfuse webhook event structure."""
id: UUID = Field(description="Event identifier")
timestamp: str = Field(description="Event timestamp")
type: str = Field(description="Event type")
action: str = Field(description="Performed action")
prompt: Dict[str, Any] = Field(description="Prompt content")
async def sync(event: LangfuseEvent) -> Dict[str, Any]:
"""Synchronize prompt data to GitHub repository."""
# Check if prompt has required label (if specified)
if config.REQUIRED_LABEL:
prompt_labels = event.prompt.get("labels", [])
if config.REQUIRED_LABEL not in prompt_labels:
return {"skipped": f"Prompt does not have required label '{config.REQUIRED_LABEL}'"}
api_endpoint = f"https://api.github.com/repos/{config.GITHUB_REPO_OWNER}/{config.GITHUB_REPO_NAME}/contents/{config.GITHUB_FILE_PATH}"
request_headers = {
"Authorization": f"Bearer {config.GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
content_json = json.dumps(event.prompt, indent=2)
encoded_content = base64.b64encode(content_json.encode("utf-8")).decode("utf-8")
name = event.prompt.get("name", "unnamed")
version = event.prompt.get("version", "unknown")
message = f"{event.action}: {name} v{version}"
payload = {
"message": message,
"content": encoded_content,
"branch": config.GITHUB_BRANCH
}
async with httpx.AsyncClient() as http_client:
try:
existing = await http_client.get(api_endpoint, headers=request_headers, params={"ref": config.GITHUB_BRANCH})
if existing.status_code == 200:
payload["sha"] = existing.json().get("sha")
except Exception:
pass
try:
response = await http_client.put(api_endpoint, headers=request_headers, json=payload)
response.raise_for_status()
return response.json()
except Exception as e:
raise HTTPException(status_code=500, detail=f"Repository sync failed: {str(e)}")
app = FastAPI(title="Langfuse GitHub Sync", version="1.0")
@app.post("/webhook/prompt", status_code=201)
async def receive_webhook(event: LangfuseEvent = Body(...)):
"""Process Langfuse webhook and sync to GitHub."""
result = await sync(event)
return {
"status": "synced",
"commit_info": result.get("commit", {}),
"file_info": result.get("content", {})
}
@app.get("/status")
async def health_status():
"""Service health check."""
return {"healthy": True}
서버는 웹훅 페이로드를 검증하고, 필요하면 기존 파일 SHA를 조회하며, 설명적인 커밋 메시지로 프롬프트 변경을 GitHub에 커밋해요.
의존성
의존성을 설치하세요:
pip install fastapi uvicorn pydantic-settings httpx
로컬에서 실행
로컬에서 실행:
uvicorn main:app --reload --port 8000
http://localhost:8000/health에서 헬스 엔드포인트를 테스트하세요. 웹훅 테스트를 위해 ngrok 등을 사용해 localhost를 노출하세요.
4단계: 서버 배포 및 연결
- 배포: Render, Fly.io, Heroku 등을 사용하세요. 환경 변수를 설정하고 HTTPS를 활성화하세요.
- 웹훅 업데이트: Langfuse에서 웹훅을 편집하고 URL을 https://your-domain.com/webhook/prompt로 설정하세요.
- 테스트: Langfuse에서 프롬프트를 업데이트하고 GitHub 저장소에 새 커밋이 나타나는지 확인하세요.
보안 고려사항
- 서명 검증: 서명 비밀과
x-langfuse-signature헤더를 사용해 요청을 검증하세요 - PAT 범위 제한: 특정 저장소로 제한된 fine-grained 토큰을 사용하세요
- 재시도 처리: 구현은 멱등(idempotent)이라 중복 이벤트가 충돌하는 커밋을 만들지 않아요