Skip to main content

Humanloop에서 W&B Weave로 마이그레이션하는 방법

이 글은 Humanloop에서 W&B Weave로 trace, prompt, dataset, evaluator, evaluation을 마이그레이션하여 이전 작업을 보존하면서도 지속적인 개발과 모니터링에 더 강력한 환경을 확보하는 방법을 설명합니다. 이 문서는 AI 번역본입니다. 오역이 의심되는 부분이 있다면 댓글로 알려주세요.
Created on September 12|Last edited on September 12
최근 Anthropic의 Humanloop 인수로 인해 해당 플랫폼은 더 이상 사용자에게 제공되지 않습니다. 그동안 trace, prompt, dataset, evaluation 관리를 Humanloop에 의존해 왔다면, 이제 앞으로 그 작업 자산을 어디에 보관하고 계속 활용할지 결정해야 합니다. 좋은 소식은 처음부터 다시 시작할 필요가 없다는 것입니다W&B Weave는 이러한 자산을 안전하게 보관하고 활용할 수 있는 자연스러운 공간을 제공하여, 과거 실험의 연속성을 보장하는 동시에 앞으로의 실험을 위한 더욱 강력한 환경을 제공합니다.
Weave로 마이그레이션하면 개발 이력을 온전히 보존하면서, 추적을 위한 인터랙티브 UI, 내장형 피드백 수집, 버전 관리되는 프롬프트와 데이터셋, 재사용 가능한 평가기, 그리고 나란히 비교할 수 있는 평가 실행을 모두 활용할 수 있습니다. 정적인 내보내기만 남기거나 아예 접근 권한을 잃는 대신, 운영 환경에서 모델 성능을 추적하고 개선하도록 설계된 플랫폼으로 매끄럽게 이전할 수 있습니다.
이 글에서는 데이터를 W&B Weave로 마이그레이션하는 과정을 설명합니다. 참고로 실제 내보내기 파일을 확보하지 못했기 때문에, 여러분의 내보내기 파일은 구조가 다소 다를 수 있습니다. 여기에서 제시하는 Humanloop 데이터 형식은 예시로 이해하시되, Weave 업로드 코드는 자체적으로 완전하며 여러분의 데이터 형식에 맞게 조정하면 그대로 동작합니다.



목차



트레이스를 W&B Weave�� 마이그레이션하기

Humanloop의 트레이스는 AI 에이전트의 전체 수명 주기를 포착했습니다. 사용자의 입력, 시스템 프롬프트, 모든 툴 호출, 그리고 그 이후의 개선 과정까지 모두 포함되었습니다. 이는 의사 결정이 어떻게 이뤄졌는지에 대한 기록으로서, 출력 디버깅, 추론 과정 재현, 그리고 동작이 설계 목표에 부합하는지 판단하는 일을 가능하게 했습니다. 플랫폼이 종료되는 지금, 이 기록을 보존하는 것은 단순한 원시 데이터 이상의 의미가 있습니다. 애플리케이션에 대한 신뢰를 쌓기 위해 팀이 의존해 온 경로 자체가 그 안에 담겨 있기 때문입니다.
트레이스 Weave의 핵심 기능입니다. 모델, 함수, 또는 도구에 대한 모든 호출은 입력, 출력, 컨텍스트와 함께 기록할 수 있으며, 애플리케이션이 어떻게 실행되었는지를 보여 주도록 서로 연결할 수 있습니다. 이 기능은 모델이 무엇을 보았는지, 어떻게 응답했는지, 그리고 어떻게 평가되었는지를 확인하기 위해 트레이스로 되돌아갈 수 있게 해 주므로 매우 유용합니다.
이 트레이스를 Weave로 마이그레이션하는 간단한 스크립트를 작성할 수 있습니다. 각 트레이스는 부모–자식 관계를 반영할 수 있도록 중첩 가능한 호출의 시퀀스로 재구성됩니다. Humanloop ‘flow’ 호출은 사용자와의 루트 상호작용을 나타낼 수 있고, 자식 prompt 각 단계에서 모델이 어떻게 응답했는지를 호출이 보여 주며, tool 호출은 외부 조회나 계산을 포착합니다. W&B에서 Humanloop 트레이스를 재생한다는 것은 원본 로그를 순회하며 해당 호출들을 순서대로 생성하는 것을 의미합니다.
코드는 다음과 같습니다.
import weave
PROJECT = "hl_migrate"

# A more complete interaction: Flow -> prompts -> tools -> final plan
HL_LOGS = [
# root flow
{"id": "root1", "type": "flow",
"inputs": {"topic": "Tokyo trip", "days": 3, "preferences": {"kids": True, "budget_usd": 1200}},
"output": None},

# gather requirements via prompt
{"id": "p_requirements", "type": "prompt", "parent_id": "root1",
"inputs": {"messages": [
{"role": "system", "content": "You are a meticulous travel planner."},
{"role": "user", "content": "Family of 3 visiting Tokyo for 3 days. Like food, parks, tech. Budget 1200 USD."}
]},
"output": "Got it. Will plan kid friendly itinerary with food, parks, and tech within budget."},

# weather tool to inform outdoor choices
{"id": "t_weather", "type": "tool", "parent_id": "root1",
"inputs": {"tool": "weather", "city": "Tokyo", "dates": ["2025-09-10","2025-09-11","2025-09-12"]},
"output": {"daily": [
{"date": "2025-09-10", "forecast": "Sunny", "high_c": 29},
{"date": "2025-09-11", "forecast": "Partly Cloudy", "high_c": 28},
{"date": "2025-09-12", "forecast": "Light Rain", "high_c": 26}
]}},

# attractions search tool
{"id": "t_search_attractions", "type": "tool", "parent_id": "root1",
"inputs": {"tool": "search_attractions", "city": "Tokyo",
"filters": {"kid_friendly": True, "themes": ["parks","science","animals","interactive"]}},
"output": [
{"name": "Ueno Zoo", "area": "Ueno"},
{"name": "teamLab Planets", "area": "Toyosu"},
{"name": "Odaiba Seaside Park", "area": "Odaiba"},
{"name": "Miraikan", "area": "Odaiba"},
{"name": "Asakusa Senso-ji", "area": "Asakusa"}
]},

# first draft plan from model
{"id": "p_draft", "type": "prompt", "parent_id": "root1",
"inputs": {"messages": [
{"role": "system", "content": "Create a 3 day Tokyo itinerary using the weather and attraction data."},
{"role": "user", "content": "Prefer short transfers, street food, and a couple of museums."}
]},
"output": "Draft: Day 1 Asakusa and Ueno. Day 2 Odaiba. Day 3 Toyosu and Shibuya."},

# distances to tighten transfers
{"id": "t_maps", "type": "tool", "parent_id": "root1",
"inputs": {"tool": "maps_batch_dist",
"legs": [
["Asakusa","Ueno"], ["Ueno","Akihabara"], ["Toyosu","Odaiba"], ["Odaiba","Shibuya"]
]},
"output": {"km": [2.2, 2.1, 5.0, 10.8]}},

# simple budget calculator
{"id": "t_budget", "type": "tool", "parent_id": "root1",
"inputs": {"tool": "budget_calc",
"items": [
{"label": "hotel_2n", "usd": 600},
{"label": "food", "usd": 300},
{"label": "transport", "usd": 90},
{"label": "tickets_misc", "usd": 140}
]},
"output": {"total_usd": 1130, "under_budget": True}},

# refine with rain day handling and distances
{"id": "p_refine", "type": "prompt", "parent_id": "root1",
"inputs": {"messages": [
{"role": "system", "content": "Refine the itinerary. Use indoor-heavy activities on the rain day."},
{"role": "user", "content": "Rain likely on Day 3. Keep transfers short."}
]},
"output": "Refined: Day 1 Asakusa-Ueno. Day 2 Odaiba (Miraikan). Day 3 Toyosu (teamLab Planets) then Shibuya."},

# final plan summary
{"id": "p_final", "type": "prompt", "parent_id": "root1",
"inputs": {"messages": [
{"role": "system", "content": "Return the final plan with times, food picks, and short notes."},
{"role": "user", "content": "Please include breakfast, lunch, dinner recs near each area."}
]},
"output":
"Day 1 Asakusa and Ueno. Morning Senso-ji and Nakamise snacks. Lunch Ueno Ameyoko stalls. "
"Afternoon Ueno Zoo and Ueno Park. Dinner Akihabara ramen. "
"Day 2 Odaiba. Morning Miraikan interactive exhibits. Lunch Aqua City food court. "
"Afternoon Odaiba Seaside Park and Palette Town. Dinner DiverCity casual Japanese. "
"Day 3 Toyosu and Shibuya. Morning teamLab Planets (indoor, good for rain). "
"Lunch Toyosu Market sushi. Afternoon Shibuya Crossing and Miyashita Park playground. "
"Dinner Shibuya yakitori alley. Total budget about 1130 USD within target. Transfers kept short where possible."
}
]

def op_name(l):
t = l.get("type")
return "flow" if t=="flow" else "prompt" if t=="prompt" else "tool"

def main():
client = weave.init(PROJECT)
made = {}
for l in HL_LOGS:
parent_call = made.get(l.get("parent_id"))
call = client.create_call(
op=op_name(l),
inputs=l.get("inputs", {}),
parent=parent_call
)
client.finish_call(call, output=l.get("output"))
made[l["id"]] = call
print("uploaded", len(HL_LOGS), "calls to", PROJECT)

if __name__ == "__main__":
main()
Humanloop가 종료된 뒤 읽기 어렵게 되는 정적 JSON 파일이나 레거시 내보내기 대신, 여러분의 트레이스는 W&B 인터페이스에서 상호작용 방식으로 보존됩니다. 어떤 호출이든 열어 정확한 프롬프트 텍스트와 모델의 응답을 확인하고, 도구들이 어떻게 체이닝되었는지도 탐색할 수 있습니다.
트레이스를 W&B로 마이그레이션한 뒤에는 UI로 이동하여 각 트레이스를 시각화할 수 있습니다!


마이그레이션 스크립트는 과거 Humanloop 로그를 Weave로 재구성하여 기존 트레이스를 계속 접근할 수 있게 합니다. 이 과정은 이력을 보존하는 데 유용하지만, 앞으로 일반적으로 트레이스를 수집하는 방식은 아닙니다. 프로덕션 환경에서는 트레이스 로깅이 훨씬 간단하며, 필요한 것은 추가하는 것뿐입니다. @weave.op 관심 있는 함수에 데코레이터를 추가하는 것.
사용하기 @weave.op 추가 보일러플레이트 코드 없이도 모든 호출이 자동으로 트레이스로 로깅되도록 보장합니다. 함수가 실행될 때마다 입력과 출력이 캡처되고, 호출 그래프가 즉석에서 구성됩니다. 이는 마이그레이션된 트레이스를 살펴보던 것과 동일한 방식으로 실시간 트래픽을 모니터링할 수 있으면서도, 수작업 복원 작업은 전혀 필요 없다는 뜻입니다.
예시는 다음과 같습니다:
import weave
from openai import OpenAI

# initialize weave once at the start of your program
weave.init("production-traces")

client = OpenAI()

# wrap your inference function with @weave.op
@weave.op
def run_model(prompt: str) -> str:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
)
return response.choices[0].message.content

# every call will now be automatically logged to Weave
print(run_model("Write me a haiku about sushi."))
많은 워크로드에서는 아예 직접 함수에 데코레이터를 붙일 필요도 없습니다. Weave는 다음과 같은 인기 있는 LLM 라이브러리들과 네이티브로 통합되어 있습니다 openai, anthropic, 그리고 litellm. weave를 import하고 호출하면 weave.init(...), 해당 클라이언트를 통해 이루어지는 모든 요청은 자동으로 추적됩니다. 즉, 거의 코드 변경 없이도 트레이싱을 시작할 수 있으며, 프로덕션 트래픽에 대한 가시성을 즉시 확보할 수 있습니다.
UI에서 임의의 호출을 열어 엄지손가락 올리기/내리기 같은 반응을 추가하거나, 무엇이 잘됐고 무엇이 실패했는지 설명하는 메모를 남길 수 있습니다. 이러한 주석은 트레이스와 함께 인라인으로 저장되며, 입력과 출력을 확인할 때마다 볼 수 있습니다. 그 결과, 모델의 동작과 인간 평가가 즉시 연결됩니다.

SDK를 사용하면 피드백을 프로그래밍 방식으로 추가하거나 조회할 수 있습니다. 이는 Humanloop에서 수집한 평가를 가져오거나 테스트 중 피드백 캡처를 자동화하려는 경우에 유용합니다. 커스텀 페이로드를 로깅하고, 반응 유형으로 필터링하며, 주석이 달린 호출들의 데이터셋을 구축할 수 있습니다. 이렇게 만든 데이터셋은 이후 평가, 파인튜닝, 디버깅 파이프라인에서 재사용할 수 있습니다.

프롬프트 마이그레이션

프롬프트는 모델의 동작을 규정하며, 이를 마이그레이션하면 공들여 만든 지시문 설계 작업이 사라지지 않도록 보존할 수 있습니다. Humanloop에서는 프롬프트가 보통 단순한 원문 텍스트 이상을 포함했습니다. 모델 이름, temperature, 소유자 정보, 버전 식별자, 조직화를 위한 태그 같은 구성 정보가 함께 들어 있는 경우가 많았습니다. 마이그레이션 이후에도 이 정보를 유용하게 유지하려면, 프롬프트 본문과 별도로 해당 메타데이터를 Weave에서 별도 객체로 게시하여 함께 보관할 수 있습니다.
Weave에서는 프롬프트가 …로 표현됩니다 weave.StringPrompt, 안정적인 이름으로 게시하여 여러 프로젝트에서 재사용할 수 있습니다. Humanloop의 추가 메타데이터는 Python 딕셔너리로 묶어 동일한 방식으로 게시할 수 있습니다. 이렇게 하면 텍스트와 구성 정보를 논리적으로 연결하되 모듈화하여, 프롬프트를 생성된 맥락을 유지한 채로 검색하고 버전 관리하며 실험할 수 있습니다.
다음은 Weave에 프롬프트를 업로드하는 방법을 보여 주는 코드입니다:
import weave

# Init your project
weave.init("intro-example")

# Publish a StringPrompt under a name
pirate_prompt = weave.StringPrompt("You are a pirate. Rewrite: {text} twice")
weave.publish(pirate_prompt, name="pirate_prompt")

# Publish your metadata as just another object under its own name
meta = {
"config": {
"model": "gpt-4o-mini",
"temperature": 0.2,
"meta": {
"team": "search",
"owner": "brett",
"source": "humanloop-migration",
"humanloop": {"file_id": "pr_123", "version_id": "v_456"},
},
},
"tags": ["summary", "imported"],
}
weave.publish(meta, name="pirate_prompt_meta")

# ----------------------
# Later, load both back
# ----------------------
fetched_prompt = weave.ref("pirate_prompt").get()
fetched_meta = weave.ref("pirate_prompt_meta").get()

print("Owner from meta:", fetched_meta["config"])
print("Prompt output:", fetched_prompt.format(text="The Eiffel Tower is in Paris."))
Weave는 프롬프트를 저장하기 위한 다른 형식들도 지원합니다. 자세한 내용은 문서를 참조하세요. 여기.
💡
이 방식은 Humanloop가 프롬프트를 저장하던 방법과 동일합니다. StringPrompt에는 실제 지시문이 담기고, 메타데이터 객체에는 프롬프트 사용 방식을 정의하던 나머지 모든 정보가 기록됩니다. 마이그레이션이 완료되면 두 객체 모두 평가에서 참조하거나 프로덕션 코드에 포함할 수 있습니다.

데이터세트 마이그레이션

Weave에서는 평가를 실행하기 위해 반드시 데이터세트가 필요하지 않습니다. 게시하지 않고도 행을 바로 평가에 전달할 수 있습니다. 다만, 시간이 지나면서 데이터를 추적하고자 한다면 데이터세트를 Weave로 마이그레이션하여 버전 관리하고 프로젝트에 연결해 두는 것이 좋습니다.
Weave의 데이터세트는 다음과 같은 필드를 가진 행들의 목록일 뿐입니다 prompt_text 그리고 target_text게시가 완료되면 UI에 표시되며 여러 평가에서 재사용할 수 있습니다. 다음은 데이터세트를 Weave에 업로드하는 방법을 보여 주는 코드입니다:
import json
import weave
from weave import Dataset

# put your Humanloop JSON inline or load from file
hl_json = {
"datapoints": [
{
"messages": [
{
"role": "user",
"content": "How do i manage my organizations API keys?\n"
}
],
"target": {
"response": "Hey, thanks for your questions. Here are steps..."
}
},
{
"messages": [
{
"role": "user",
"content": "Hey, can do I use my code evaluator for monitoring my legal-copilot prompt?"
}
],
"target": {
"response": "Hey, thanks for your questions. Here are steps..."
}
}
],
"path": "datasets/support-queries",
"version_name": "Initial version",
"version_description": "Add two new questions and answers"
}

def to_weave_rows(hl_obj):
rows = []
for i, dp in enumerate(hl_obj.get("datapoints", [])):
messages = dp.get("messages") or []
target = dp.get("target") or {}
inputs = dp.get("inputs") or {}

prompt_text = ""
if messages:
parts = []
for m in messages:
role = m.get("role", "")
content = m.get("content", "")
parts.append(f"{role}: {content}".strip())
prompt_text = "\n".join(parts)
elif inputs:
prompt_text = json.dumps(inputs, ensure_ascii=False)

if isinstance(target, dict):
target_text = target.get("response") or ""
else:
target_text = str(target)

row = {
"id": str(i),
"messages": messages,
"inputs": inputs,
"prompt_text": prompt_text,
"target_text": target_text
}
rows.append(row)
return rows

# init your Weave project
weave.init("hl_migrate")

rows = to_weave_rows(hl_json)

dataset_name = hl_json.get("path", "humanloop_import").split("/")[-1]
dataset = Dataset(name=dataset_name, rows=rows)

# publish to Weave
weave.publish(dataset)

print(f"Uploaded {len(rows)} rows to weave dataset '{dataset.name}'")
각 행은 해당 프롬프트와 타깃과 함께 열람할 수 있으며, 데이터세트는 버전 관리되어 평가에서 재사용할 수 있습니다.

데이터세트를 Weave에 업로드한 뒤에는 곧바로 평가를 실행하는 데 사용할 수 있습니다. 데이터세트는 행을 체계적으로 관리하고 버전 관리를 제공하며, 평가는 모델이 해당 데이터에서 어떻게 성능을 내는지 측정할 수 있게 해줍니다.
예를 들어 다음과 같습니다:
import asyncio
import weave

weave.init("hl_migrate")

# retrieve the published dataset version
dataset_v0 = weave.ref("support-queries:v1").get()

# simple model wrapped as a weave op
@weave.op
def my_model(prompt_text: str) -> str:
# replace with your real model call
return f"echo: {prompt_text}"

# scorer that compares predicted text to the gold text
@weave.op
def exact_match(target_text: str, output: str) -> float:
return float(target_text.strip().lower() == output.strip().lower())

# map each dataset row to the model input args
def preprocess_model_input(row: dict):
# the converter script created prompt_text and target_text fields
return {"prompt_text": row.get("prompt_text", "")}

evaluation = weave.Evaluation(
name="support-queries-eval",
dataset=dataset_v0,
scorers=[exact_match],
preprocess_model_input=preprocess_model_input,
)

async def main():
await evaluation.evaluate(my_model)
print("evaluation complete. check Weave UI under Evaluations in byyoung3/my-project")

if __name__ == "__main__":
asyncio.run(main())
이 스크립트는 Weave에서 데이터세트를 불러와 각 행에 대해 모델을 실행하고, 성능을 측정하기 위해 scorer 함수를 적용합니다. 평가가 완료되면 결과는 Weave UI에 바로 표시되며, 예시별 점수를 확인하거나 여러 평가 실행을 비교할 수 있습니다. 아래는 비교 뷰에서 데이터세트의 각 샘플에 대한 입력과 출력을 시각화하는 방법을 보여 주는 스크린샷입니다:


Humanloop 평가자 마이그레이션

Evaluators Humanloop에서는 모델 출력의 평가 기준을 정의했습니다. 일부는 단순한 Python 함수였고, 다른 일부는 LLM을 사용해 자연어로 판단을 내렸습니다. 그 작업을 보존하려면 평가자를 Weave로 마이그레이션하여 재사용 가능한 객체로 유지할 수 있습니다.
Weave에서는 평가자를 해당 사양을 설명하는 일반 Python 딕셔너리로 저장한 뒤, 안정적인 이름으로 게시할 수 있습니다. 업로드가 완료되면 다시 가져와 Scorer 클래스 안에 감쌀 수 있습니다. Scorer는 @로 데코레이트된 score 메서드를 정의합니다.weave.op그래서 각 평가 실행도 모델 호출과 동일하게 기록됩니다.
다음은 Python 기반 정확도 평가자와 LLM 기반 평가자를 게시한 뒤 점수 산정에 사용하는 예시입니다.
import weave
from weave import Scorer
from litellm import completion
from pydantic import Field

weave.init("hl_migrate")

# ----------------------------
# Evaluator Specs
# ----------------------------
python_evaluator = {
"spec": {
"arguments_type": "target_required",
"return_type": "number",
"evaluator_type": "python",
"code": "def evaluate(answer, target):\n return 1.0 if answer.strip()==target.strip() else 0.0",
"number_valence": "positive"
},
"path": "Shared Evaluators/Simple Accuracy",
"version_name": "v1",
"version_description": "Python accuracy evaluator"
}

llm_evaluator = {
"spec": {
"arguments_type": "target_required",
"return_type": "text",
"evaluator_type": "llm",
"prompt": {
"provider": "openai",
"model": "gpt-4o-mini",
"system_prompt": "You are a helpful evaluator. Judge correctness of {answer} against {target}.",
"user_prompt": "Answer: {answer}\nTarget: {target}\nGive a 1 sentence judgment."
}
},
"path": "Shared Evaluators/LLM Judge",
"version_name": "v1",
"version_description": "LLM based evaluator"
}

# ----------------------------
# Publish evaluators to Weave
# ----------------------------
weave.publish(python_evaluator, name="simple_accuracy_eval")
weave.publish(llm_evaluator, name="llm_judge_eval")

# ----------------------------
# Retrieve them back down
# ----------------------------
fetched_python_eval = weave.ref("simple_accuracy_eval").get()
fetched_llm_eval = weave.ref("llm_judge_eval").get()

# ----------------------------
# Python Evaluator Scorer
# ----------------------------
class PythonEvaluatorScorer(Scorer):
evaluator: dict = Field(...)

@weave.op
def score(self, answer: str, target: str) -> dict:
spec = self.evaluator["spec"]
try:
local_env = {}
exec(spec["code"], {}, local_env)
result = local_env["evaluate"](answer, target)
return {"score": result}
except Exception as e:
return {"error": str(e)}

# ----------------------------
# LLM Evaluator Scorer
# ----------------------------
class LLMEvaluatorScorer(Scorer):
evaluator: dict = Field(...)

@weave.op
def score(self, answer: str, target: str) -> dict:
spec = self.evaluator["spec"]
try:
user_prompt = spec["prompt"]["user_prompt"].format(answer=answer, target=target)
system_prompt = spec["prompt"]["system_prompt"]
response = completion(
model=spec["prompt"]["model"],
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
temperature=0,
max_tokens=200,
)
return {"judgment": response["choices"][0]["message"]["content"].strip()}
except Exception as e:
return {"error": str(e)}

# ----------------------------
# Example Usage
# ----------------------------
if __name__ == "__main__":
answer, target = "Hello world", "Hello world!"

# use fetched evaluators from weave
py_scorer = PythonEvaluatorScorer(evaluator=fetched_python_eval)
llm_scorer = LLMEvaluatorScorer(evaluator=fetched_llm_eval)

print("Python Evaluator:", py_scorer.score(answer, target))
print("LLM Evaluator:", llm_scorer.score(answer, target))
이 설정을 통해 Humanloop의 평가자를 Weave 평가 내에서 재사용할 수 있습니다. 데이터세트와 프롬프트와 마찬가지로, 평가자는 버전이 관리되는 객체로 저장되며 이름으로 조회할 수 있고 실행 시 UI에서 추적됩니다. 이를 통해 실험 간 점수 산정 로직을 손쉽게 비교하거나 프로젝트 간에 공통 평가자를 공유할 수 있습니다.

평가 마이그레이션

Humanloop의 평가에서는 데이터세트, 모델, 그리고 하나 이상의 평가자가 함께 연결되었습니다. 각 실행(run)은 예측값과 점수를 함께 저장했습니다. Weave로 마이그레이션할 때 이 구조를 바꿀 필요는 없으며, 다만 이를 Weave를 사용해 동일하게 다시 표현하면 됩니다. EvaluationLogger.
아래 스크립트는 메타데이터와 로그를 포함한 과거 Humanloop 평가를 Weave로 가져오는 방법을 보여줍니다. 각 예측은 입력, 출력, 그리고 평가자 점수와 함께 기록됩니다. 마지막에는 요약을 계산해 게시하여 전체 실행(run)이 Weave의 Evaluations 탭에 나타나도록 합니다.
import weave
from weave import EvaluationLogger
from statistics import mean

# -----------------
# HARD CODE DATA
# -----------------
WEAVE_PROJECT = "hl_migrate"

EVAL_META = {
"id": "evr_12345",
"model": "gpt5",
"name": "Prompt Quality Evaluation",
"file_id": "pr_67890",
"created_at": "2025-06-05T12:00:00Z",
"description": "Evaluation of prompt outputs against reference answers",
"status": "completed",
"config": {
"evaluators": ["accuracy", "helpfulness"],
"split": "validation"
}
}

LOGS = [
{"id": "log_001", "input": "What is the capital of France?", "output": "Paris",
"target": "Paris", "evaluators": {"accuracy": 1, "helpfulness": 1}},
{"id": "log_002", "input": "2+2?", "output": "4",
"target": "4", "evaluators": {"accuracy": 0, "helpfulness": 0}},
{"id": "log_003", "input": "2+3?", "output": "6",
"target": "5", "evaluators": {"accuracy": 0, "helpfulness": 0}},
{"id": "log_004", "input": "Summarize 'Romeo and Juliet'", "output": "Two lovers die tragically.",
"target": "Romeo and Juliet is a tragedy about two lovers whose deaths reconcile their feuding families.",
"evaluators": {"accuracy": 1, "helpfulness": 1}},
{"id": "log_005", "input": "What is the capital of Spain?", "output": "Madrid",
"target": "Madrid", "evaluators": {"accuracy": 1, "helpfulness": 1}},
{"id": "log_006", "input": "Translate 'hello' to French", "output": "bonjour",
"target": "bonjour", "evaluators": {"accuracy": 1, "helpfulness": 1}},
{"id": "log_007", "input": "Square root of 16?", "output": "4",
"target": "4", "evaluators": {"accuracy": 1, "helpfulness": 1}},
{"id": "log_008", "input": "Capital of Germany?", "output": "Berlin",
"target": "Berlin", "evaluators": {"accuracy": 1, "helpfulness": 1}},
{"id": "log_009", "input": "What is 10/0?", "output": "Infinity",
"target": "undefined", "evaluators": {"accuracy": 0, "helpfulness": 0}},
{"id": "log_010", "input": "Largest planet in solar system?", "output": "Jupiter",
"target": "Jupiter", "evaluators": {"accuracy": 1, "helpfulness": 1}}
]
# -----------------

def summarize(all_scores):
agg = {}
if not all_scores:
return agg
keys = set().union(*[d.keys() for d in all_scores if isinstance(d, dict)])
for k in keys:
vals = []
for d in all_scores:
if not isinstance(d, dict):
continue
v = d.get(k)
if isinstance(v, bool):
vals.append(1.0 if v else 0.0)
elif isinstance(v, (int, float)):
vals.append(float(v))
if vals:
agg[k + "_mean"] = mean(vals)
return agg

def main():
weave.init(WEAVE_PROJECT)

model_name = EVAL_META.get("model") or EVAL_META.get("config", {}).get("model") or "unknown_model"
dataset_name = EVAL_META.get("config", {}).get("dataset") or EVAL_META["name"]

logger = EvaluationLogger(model=model_name, dataset=dataset_name)

per_pred_scores = []
for log in LOGS:
inputs = {"input": log["input"], "target": log["target"]}
output = log.get("output")
pred = logger.log_prediction(inputs=inputs, output=output)

scores = log.get("evaluators", {})
for scorer, score in scores.items():
val = float(score) if isinstance(score, (int, float)) else (1.0 if score else 0.0)
pred.log_score(scorer=str(scorer), score=val)
per_pred_scores.append(scores)

pred.finish()

summary = summarize(per_pred_scores)
summary["source_file_id"] = EVAL_META["file_id"]
summary["humanloop_eval_name"] = EVAL_META["name"]
logger.log_summary(summary)

print("migration complete. check Weave Evals tab")

if __name__ == "__main__":
main()
이는 과거 결과를 보존하면서도 Weave의 네이티브 평가로 계속 진행할 수 있게 해 줍니다. 기존 Humanloop 점수는 계속 확인할 수 있으며, Weave로 가져오면 새로운 실행과 비교하고, 동일한 데이터세트를 재사용하거나 마이그레이션한 평가자로 교체해 사용할 수 있습니다.
마이그레이션이 완료되면 평가 항목이 Weave에 표시되며, 서로 다른 모델을 나란히 비교할 수 있습니다. 요약 보기에서는 정확도와 유용성 같은 집계 지표를 막대 차트와 레이더 차트로 함께 제공하여, 새로운 모델이 기준선 대비 향상되었는지 또는 특정 지표가 퇴행했는지를 한눈에 파악할 수 있습니다.



결론

Humanloop에서 Weave로 전환한다고 해서 지금까지의 작업을 잃는다는 뜻은 아닙니다. 트레이스, 프롬프트, 데이터세트, 평가자, 평가 항목을 마이그레이션하면 과거 실험을 보존하면서 향후 개발을 위한 더 강력한 도구를 활용할 수 있습니다. 정적인 내보내기 결과만 남는 대신, 모든 호출을 추적하고 평가하며 개선할 수 있는 새로운 대화형 환경을 얻게 됩니다.
Weave는 과거 이력을 안전하게 보존할 뿐만 아니라 실시간 가시성, 피드백 수집, 인기 LLM 라이브러리와의 매끄러운 통합까지 제공합니다. 단순한 마이그레이션 작업으로 시작했더라도 곧 업그레이드로 이어집니다. 기존 워크플로는 그대로 유지되면서, 이제는 지속적인 실험과 프로덕션 모니터링을 위해 설계된 플랫폼에 연결됩니다.

이 문서는 AI 번역본입니다. 오역이 있을 수 있으니 댓글로 알려주세요. 원문 보고서는 아래 링크에서 확인하실 수 있습니다: 원문 보고서 보기