학습·시험

하네스 엔지니어링 워크북

LLM 에이전트 하네스(대화 배열·도구·루프·컨텍스트 관리·RAG·온톨로지)를 직접 구현해 보는 파이썬 실습 워크북이며, 에이전트를 만드는 개발자를 위한 것입니다.

분야학습·시험플랫폼Web기술Python 표준 라이브러리 · Pyodide · Vanilla JS · unittest · localStorage · 정적 사이트 빌더(Python)
11개 랩 목차: 개념 → 예제 → 퀴즈 → 코드 채우기 → 자동 채점 순서.
11개 랩 목차: 개념 → 예제 → 퀴즈 → 코드 채우기 → 자동 채점 순서.
Lab 03 에이전트 루프: 예제 코드를 브라우저에서 바로 실행하고 결과를 확인합니다.
Lab 03 에이전트 루프: 예제 코드를 브라우저에서 바로 실행하고 결과를 확인합니다.

왜 만들었나

에이전트 프레임워크는 내부가 감춰져 있어 루프와 컨텍스트가 실제로 어떻게 움직이는지 알기 어렵고, 실습하려면 API 키와 로컬 환경이 필요합니다. 표준 라이브러리만 쓴 작은 하네스를 랩별로 직접 채우고 브라우저에서 바로 채점하게 했습니다.

주요 기능

  • 11개 랩: 대화 배열, 도구, 에이전트 루프, 컨텍스트 예산, 서브에이전트, 조종, 가드레일, 디버깅, RAG, 온톨로지, 캡스톤
  • Pyodide로 브라우저 안에서 unittest 채점, 실패 원인을 짧은 힌트로 요약
  • API 키 없이 동작하는 FakeBrain·ScriptedBrain 두뇌
  • 편집 중인 코드 자동 저장, 캡스톤에서 앞 랩의 내 코드를 조립
  • 랩별 퀴즈와 바로 실행해 볼 수 있는 따라 해보기 예제

숫자로 보면

실습 랩 11개, 채점 테스트 56개
랩 퀴즈 22문항, 따라 해보기 예제 34개
RAG 실습용 구매 업무 문서 8개(BM25 검색)

어떻게 동작하나

  1. 사이트 빌드

    build_site.py가 랩별 마크다운을 HTML로 바꾸고 실습 파이썬 파일 전체를 assets/files.json 하나로 묶습니다.

    project/scripts/build_site.py
  2. Pyodide 준비

    채점을 처음 누르면 Pyodide를 내려받고 files.json의 파일을 가상 파일시스템 /harness-lab 아래에 씁니다.

    assets/app.js
  3. 내 코드 주입

    모든 랩의 exercise.py를 localStorage에 저장된 내 코드로 덮어쓰고, 캡스톤에서는 안 푼 랩을 정답으로 채웁니다.

    assets/app.js
  4. 환경 맞추기

    브라우저에 없는 subprocess.run을 echo·ls·cat·pwd만 흉내 내는 함수로 바꾸고 이전 실행의 모듈 캐시를 지웁니다.

    assets/runner.py
  5. 테스트 실행

    랩의 test_lab.py를 unittest로 돌려 테스트별 통과 여부와 오류 내용을 JSON으로 돌려줍니다.

    assets/runner.py
  6. 결과 표시

    통과 수와 실패 힌트를 그리고, 모두 통과하면 그 랩을 완료로 표시합니다.

    assets/app.js

핵심 코드

project/common/loop.pyGitHub에서 보기 ↗
for _ in range(max_rounds):
    response = brain({"system": system, "messages": messages, "tools": tools})
    messages.append({"role": "assistant", "content": response["content"]})
    for b in response["content"]:
        if b["type"] == "text" and b["text"].strip():
            emit("text", b["text"])
    if response.get("stop_reason") != "tool_use":
        return final_text(response)

    results = []
    for b in response["content"]:
        if b["type"] == "tool_use":
            emit("tool", b)
            results.append({"type": "tool_result", "tool_use_id": b["id"],
                            "content": execute(b["name"], b["input"])})
    if inject:
        inject(results)
    messages.append({"role": "user", "content": results})

모델 호출 → tool_use 실행 → tool_result를 배열에 덧붙이기를 stop_reason이 바뀔 때까지 반복하는 에이전트 루프의 참고 구현입니다.

어려웠던 점

로컬 파이썬용으로 쓴 실습 코드를 브라우저(Pyodide)에서 그대로 채점하는 부분입니다. subprocess를 흉내 낸 함수로 바꾸고, 실행마다 sys.modules와 sys.path를 정리해 이전 코드가 남지 않게 했습니다.