Rhesis로 Haystack 파이프라인 추적하기

Rhesis로 Haystack 파이프라인 추적하기

Rhesis는 LLM 에이전트에 대한 구조화된 피드백과 평가를 제공하는 오픈소스 플랫폼이에요. 이번에는 Haystack 파이프라인과 Agent 실행을 Rhesis에서 추적하는 방법을 함께 알아볼게요. OpenTelemetry 기반으로 동작해서, 트레이스를 확인하고 평가 데이터와 연결하는 것까지 자연스럽게 이어져요. 이 통합에서 사용하는 트레이서 클래스는 RhesisTracer이고, 파이프라인에서는 RhesisConnector를 추가하는 것만으로 활성화되며, 파이프라인 밖에서 Haystack을 구동하는 애플리케이션이라면 RhesisTracing을 사용하면 돼요.

출처: 공식문서

개요

Rhesis에서 Haystack 파이프라인, 컴포넌트, 그리고 Agent 실행을 추적할 수 있어요. 트레이스는 OpenTelemetry로 내보내지고, 단순히 보기만 하는 게 아니라 평가 데이터와도 연계돼요. 스팬에는 테스트 실행과 대화 턴을 식별하는 속성들이 포함되는데, 바로 rhesis.test.run_id, rhesis.test.id, rhesis.test.result_id, rhesis.conversation.id 같은 값들이에요. 덕분에 Rhesis 테스트 실행 아래에서 파이프라인을 돌리면, 리뷰어 피드백이 정확히 해당 답변을 만든 스팬 트리에 연결돼요.

또한 Haystack의 두 가지 스팬 형태를 모두 지원해요. 2.x의 배치 처리되는 ToolInvoker 컴포넌트 스팬, 그리고 3.0의 에이전트 루프 형태요. 3.0에서는 각 단계가 ai.llm.invoke 스팬이 되고, 각 도구 호출은 ai.tool.invoke 스팬이 되며, 다른 Agent를 실행하는 도구는 ai.agent.handoff로 승격돼요.

설치

rhesis-haystack 패키지를 설치하면 돼요.

pip install rhesis-haystack

사전 준비

  • Rhesis 계정이 필요해요. 또는 RHESIS_BASE_URL이 가리키는 자체 호스팅 백엔드를 사용해도 돼요.
  • RHESIS_API_KEY 환경 변수에 Rhesis API 키를 설정하세요.
  • 프롬프트와 완성 내용을 캡처하려면 HAYSTACK_CONTENT_TRACING_ENABLED 환경 변수를 true로 설정하세요.

사용 방법

트레이싱이 제대로 동작하려면 Haystack 컴포넌트를 import 하기 전에 환경 변수를 먼저 설정해야 해요. Haystack은 import 시점에 내부 트레이싱 컴포넌트를 초기화하기 때문이에요. 되도록이면 스크립트를 실행하기 전에 셸에서 환경 변수를 설정해 두는 게 더 좋아요.

선택적으로 설정할 수 있는 환경 변수는 다음과 같아요.

변수 설명
RHESIS_BASE_URL 백엔드 URL. 기본값은 http://localhost:8080
RHESIS_PROJECT_ID 프로젝트 ID. 생략하면 API 키에서 자동으로 해석돼요.
RHESIS_ENVIRONMENT 환경 라벨. 기본값은 development
RHESIS_FRONTEND_URL trace_url 딥 링크를 만들 때 사용하는 프론트엔드 URL
HAYSTACK_RHESIS_ENFORCE_FLUSH 기본값은 true이며, 파이프라인 실행당 한 번 내보내요. false로 설정하면 배치 프로세서가 내보내기를 담당해요.

RhesisConnector 컴포넌트를 파이프라인에 추가하세요. 다른 컴포넌트와 연결할 필요는 없어요. 이 컴포넌트를 생성하는 것만으로 모든 파이프라인 작업에 트레이서가 활성화되고, 트레이스의 name, trace_url, trace_id를 출력으로 돌려줘요.

import os

os.environ["RHESIS_API_KEY"] = "<your-api-key>"
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"

from haystack import Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage

from haystack_integrations.components.connectors.rhesis import RhesisConnector

pipe = Pipeline()
pipe.add_component("tracer", RhesisConnector("Chat example"))
pipe.add_component("prompt_builder", ChatPromptBuilder())
pipe.add_component("llm", OpenAIChatGenerator(model="gpt-4o-mini"))
pipe.connect("prompt_builder.prompt", "llm.messages")

messages = [
    ChatMessage.from_system(
        "Always respond in German even if some input data is in other languages.",
    ),
    ChatMessage.from_user("Tell me about {{location}}"),
]

response = pipe.run(
    data={
        "prompt_builder": {
            "template_variables": {"location": "Berlin"},
            "template": messages,
        },
        "tracer": {"invocation_context": {"session_id": "demo-session"}},
    },
)
print(response["llm"]["replies"][0])
print(response["tracer"]["trace_url"])
print(response["tracer"]["trace_id"])

파이프라인을 실행할 때마다 하나의 트레이스가 생성돼요. 루트는 function.haystack.pipeline.run 스팬이고, 각 컴포넌트마다 자식 스팬이 하나씩 생겨요. 생성기는 모델 이름과 토큰 수를 담은 ai.llm.invoke 스팬이 되고, 검색기는 ai.retrieval, 임베더는 ai.embedding.generate 스팬이 돼요.

invocation_context 입력은 실행의 루트 스팬에 메타데이터를 첨부해요. 키가 session_id, conversation_id, test_run_id, test_id, test_result_id, test_configuration_id라면 Rhesis의 일급 속성이 되고, 그 외의 키는 haystack.invocation.<key> 형태로 전달돼요.

Agent 추적하기

RhesisConnector를 생성하는 것 자체가 트레이서를 활성화하는 거라서, 독립형 Agent는 따로 할 일이 없어요. 커넥터를 만들어 두기만 하면 돼요. 파이프라인이 없으니 invocation_context 입력을 전달할 방법이 없어요. 그럴 때는 rhesis_invocation_context 컨텍스트 매니저를 사용해서 메타데이터를 붙이면 돼요.

from haystack.dataclasses import ChatMessage
from haystack_integrations.components.connectors.rhesis import RhesisConnector
from haystack_integrations.tracing.rhesis import rhesis_invocation_context

RhesisConnector("Agent example")  # enables the tracer; never added to a pipeline

with rhesis_invocation_context({"session_id": "agent-example", "test_run_id": "tr-1"}):
    result = agent.run(
        messages=[ChatMessage.from_user("What is the weather in Berlin?")]
    )

블록 안에서 열리는 모든 스팬은 해당 세션에 속하게 되고, 블록을 빠져나가면 이전 컨텍스트로 복원돼요. 이 컨텍스트 매니저는 pipeline.run() 호출을 감쌀 때도 사용할 수 있어요. 입력 소켓으로는 할 수 없는 일을 해주