OpenLLMetry
OpenLLMetry는 Traceloop 팀이 Apache 2.0 라이선스로 개발하고 유지 관리하는 도구로, OpenTelemetry의 기능을 확장해 LLM 애플리케이션을 위한 전용 모니터링 및 디버깅 도구를 제공합니다. OpenTelemetry의 표준화된 텔레메트리 데이터 형식을 활용해 LLM 애플리케이션의 핵심 성능 지표와 추적 정보를 표준화된 형태로 출력합니다.
설정¶
OTEL을 사용해 추적 데이터를 DataKit으로 전송하기 전에 Collector가 설정되어 있는지 확인하고, 다음과 같이 구성 파일의 customer_tags = ["llm.request.type","traceloop.entity.path","llm.is_streaming","gen_ai.openai.api_base","gen_ai.prompt.1.content","gen_ai.response.model","gen_ai.completion.0.content","gen_ai.request.model","gen_ai.request.temperature","gen_ai.system","traceloop.workflow.name"]를 조정해야 합니다.
[[inputs.opentelemetry]]
## customer_tags는 데이터 센터로 태그가 전송되는 것을 방지하는 화이트리스트로 작동합니다.
## 모든 . 는 _ 로 대체됩니다. 예:
customer_tags = ["llm.request.type","traceloop.entity.path","llm.is_streaming","gen_ai.openai.api_base","gen_ai.prompt.1.content","gen_ai.response.model","gen_ai.completion.0.content","gen_ai.request.model","gen_ai.request.temperature","gen_ai.system","traceloop.workflow.name"]
...
조정이 끝나면 DataKit을 재시작합니다
OpenTelemetry SDK 설치¶
pip install opentelemetry-api opentelemetry-instrumentation pip install opentelemetry-instrumentation-flask
OpenLLMetry SDK 설치¶
pip install traceloop-sdk
애플리케이션에서 OpenLLMetry 초기화¶
from traceloop.sdk import Traceloop
# OpenLit 초기화
# Traceloop.init()
Traceloop.init(app_name="kimi_openllmetry_stream_flask")
OpenLLMetry 예제 코드¶
import os
import httpx
from flask import Flask, request, Response,jsonify,stream_with_context
from traceloop.sdk import Traceloop
from traceloop.sdk.decorators import workflow,task
from openai import OpenAI
from opentelemetry.instrumentation.flask import FlaskInstrumentor
app = Flask(__name__)
# FlaskInstrumentor로 Flask 애플리케이션 자동 계측
FlaskInstrumentor().instrument_app(app)
# OpenLit 초기화
Traceloop.init(app_name="kimi_openllmetry_stream_flask")
# 환경 변수에서 API Key 가져오기
api_key = os.getenv("MOONSHOT_API_KEY")
if not api_key:
raise ValueError("MOONSHOT_API_KEY 환경 변수를 설정하세요")
client = OpenAI(
api_key=api_key,
base_url="https://api.moonshot.cn/v1",
)
def estimate_token_count(input_messages) -> int:
"""
입력 메시지의 토큰 수를 계산합니다.
"""
try:
header = {
"Authorization": f"Bearer {api_key}",
}
data = {
"model": "moonshot-v1-128k",
"messages": input_messages,
}
with httpx.Client() as client:
print("API 요청")
r = client.post("https://api.moonshot.cn/v1/tokenizers/estimate-token-count", headers=header, json=data)
r.raise_for_status()
response_data = r.json()
print(response_data["data"]["total_tokens"])
return response_data["data"]["total_tokens"]
except httpx.RequestError as e:
print(f"요청 실패: {e}")
raise
except (KeyError, ValueError) as e:
print(f"응답 파싱 실패: {e}")
raise
def select_model(input_messages, max_tokens=1024) -> str:
"""
입력 컨텍스트 메시지와 예상 max_tokens 값을 기준으로 적절한 모델을 선택합니다.
"""
if not isinstance(max_tokens, int) or max_tokens <= 0:
raise ValueError("max_tokens는 양의 정수여야 합니다")
prompt_tokens = estimate_token_count(input_messages)
total_tokens = prompt_tokens + max_tokens
if total_tokens <= 8 * 1024:
return "moonshot-v1-8k"
elif total_tokens <= 32 * 1024:
return "moonshot-v1-32k"
elif total_tokens <= 128 * 1024:
return "moonshot-v1-128k"
else:
raise ValueError("토큰 수가 제한을 초과했습니다 😢")
@app.route('/ask', methods=['POST'])
@workflow(name="ask_workflow")
def ask():
data = request.json
messages = data.get('messages')
max_tokens = data.get('max_tokens', 2048)
if not messages:
return jsonify({"error": "messages 필드는 비워둘 수 없습니다"}), 400
try:
model = select_model(messages, max_tokens)
completion = client.chat.completions.create(
model=model,
messages=messages,
max_tokens=max_tokens,
temperature=0.3,
stream=True # 스트리밍 생성을 활성화
)
def generate():
for chunk in completion:
# chunk.choices[0].delta.content 또는 ''를 반환
delta = chunk.choices[0].delta
if delta.content:
print(delta.content, end="")
yield delta.content or ''
return Response(stream_with_context(generate()), content_type='text/event-stream')
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == '__main__':
app.run(debug=True,port=5001)
env를 설정하여 데이터를 OpenTelemetry를 통해 Datakit으로 전송합니다
export TRACELOOP_BASE_URL=http://localhost:9529/otel
지표 설명¶
| 지표 이름 | 설명 | 단위 |
|---|---|---|
gen_ai.client.generation.choices |
클라이언트가 생성한 선택 항목 수 | 개 |
gen_ai.client.operation.duration_bucket |
클라이언트 작업 지속 시간의 히스토그램 버킷 | 밀리초 |
gen_ai.client.operation.duration_count |
클라이언트 작업의 총 횟수 | 회 |
gen_ai.client.operation.duration_max |
클라이언트 작업의 최대 지속 시간 | 밀리초 |
gen_ai.client.operation.duration_min |
클라이언트 작업의 최소 지속 시간 | 밀리초 |
gen_ai.client.operation.duration_sum |
클라이언트 작업의 총 지속 시간 | 밀리초 |
llm.openai.chat_completions.streaming_time_to_first_token_bucket |
OpenAI 채팅 완성 기능의 스트리밍 중 첫 토큰 생성 시간 히스토그램 버킷 | 밀리초 |
llm.openai.chat_completions.streaming_time_to_first_token_count |
OpenAI 채팅 완성 기능의 스트리밍 중 첫 토큰 생성 총 횟수 | 회 |
llm.openai.chat_completions.streaming_time_to_first_token_max |
OpenAI 채팅 완성 기능의 스트리밍 중 첫 토큰 생성 최대 시간 | 밀리초 |
llm.openai.chat_completions.streaming_time_to_first_token_min |
OpenAI 채팅 완성 기능의 스트리밍 중 첫 토큰 생성 최소 시간 | 밀리초 |
llm.openai.chat_completions.streaming_time_to_first_token_sum |
OpenAI 채팅 완성 기능의 스트리밍 중 첫 토큰 생성 총 시간 | 밀리초 |
llm.openai.chat_completions.streaming_time_to_generate_bucket |
OpenAI 채팅 완성 기능의 스트리밍 중 생성 내용 총 시간 히스토그램 버킷 | 밀리초 |
llm.openai.chat_completions.streaming_time_to_generate_count |
OpenAI 채팅 완성 기능의 스트리밍 중 생성 내용 총 횟수 | 회 |
llm.openai.chat_completions.streaming_time_to_generate_max |
OpenAI 채팅 완성 기능의 스트리밍 중 생성 내용 최대 시간 | 밀리초 |
llm.openai.chat_completions.streaming_time_to_generate_min |
OpenAI 채팅 완성 기능의 스트리밍 중 생성 내용 최소 시간 | 밀리초 |
llm.openai.chat_completions.streaming_time_to_generate_sum |
OpenAI 채팅 완성 기능의 스트리밍 중 생성 내용 총 시간 | 밀리초 |
참고 자료¶
- OpenLLMetry quickstart
- OpenLLMetry otel-collector
- OpenLLMetry github