Skip to main content
Glama

Deadwax

CI

개인 청취 기록을 다루는 에이전틱 음악 사서입니다. 무엇을 들었는지에 대한 자연어 질문에 답하고, 하드 제약 조건에 맞춰 재생 목록을 만듭니다. 결정론적 검증기, 복구 루프, 그리고 통과/실패가 아닌 통과 비율을 보고하는 평가 스위트를 갖추고 있습니다.

도구 사용, 검색, 추적, 평가, MCP 등 프로덕션 LLM 엔지니어링 실무를 보여주는 포트폴리오 작품으로 제작되었습니다.

흥미로운 부분

대부분의 LLM 데모는 두 번 실행하기 전까지만 작동합니다. Deadwax는 두 번째 실행에서 일어나는 일을 중심으로 설계되었습니다.

시스템은 한 가지 기준으로 나뉩니다: 모델은 절대 계산하지 않습니다. 모델은 언어를 해석하고, 도구를 선택하며, 산문을 작성합니다. 모든 산술 연산, 임계값 검사, 제약 조건 판정은 LLM의 존재를 모르는 순수 Python에서 이루어집니다. 재생 목록이 제약 조건을 위반하면 검증기는 False를 반환하지 않습니다. 대신 위반 코드, 문제가 되는 트랙 ID, 구체적인 해결 방법을 반환하며, 이것이 자동 복구 루프가 작동할 수 있는 유일한 이유입니다.

실행 가능성은 생성 이전에 확인됩니다. 요청을 충족할 수 없으면 시스템이 그렇게 알려줍니다. 절대 채워 넣거나, 대체하거나, 조용히 성능을 저하시키지 않습니다. status="infeasible"은 자체 평가 케이스를 가진 올바른 결과입니다.

Related MCP server: YouTube Music MCP Server

아키텍처 규칙

스타일상의 선호가 아닙니다. 이 중 하나라도 위반하면 프로젝트의 목적이 무너집니다.

  1. 모델은 절대 계산하지 않습니다.

  2. 도구 설명은 동작 제어입니다 — 프롬프트이지 문서가 아닙니다. 하나를 변경하는 것은 동작 변경이며, 병합 전에 재평가됩니다.

  3. 위반 사항은 기계가 실행 가능해야 합니다: 코드, 문제가 되는 ID, 해결 방법. 맨 불리언만으로는 안 되고, 산문만으로도 안 됩니다.

  4. 실행 가능성은 생성 전에 확인됩니다.

  5. 실패는 유효한 결과입니다.

  6. 하드 제약 조건은 측정되고 강제됩니다. 소프트 제약 조건은 추론되고 점수가 매겨지며, 이에 관한 모든 메시지가 공개해야 하는 provenance 필드를 지닙니다.

  7. 추적 없는 모델 호출은 없습니다.

결정 사항과 그 트레이드오프는 docs/adr에 기록됩니다.

스택

모든 것이 무료 티어에서 실행됩니다. 이는 선호가 아니라 하드 제약 조건입니다.

계층

선택

언어

Python 3.13

도구 체인

의존성과 Python 자체 관리를 위한 uv, 린트와 포맷을 위한 ruff, pytest

에이전트

LangChain, 3단계에서 도입되며 그 이전에는 사용하지 않음

모델

Google Gemini Flash, OpenRouter 폴백 사용

저장소

sqlite3 표준 라이브러리 모듈을 통한 SQLite

검색

빌드 타임 임베딩, 순수 Python의 코사인 유사도 — 벡터 데이터베이스 없음

추적

Langfuse

상호 운용

Model Context Protocol 서버

프론트엔드

Cloudflare Pages의 Astro — 유일한 JavaScript 구성 요소

0단계부터 2단계까지는 표준 라이브러리만 사용합니다 — 프레임워크, SDK, 의존성이 없습니다. 어떤 추상화도 도입되기 전에 원시 HTTP 호출을 직접 작성하므로, 프레임워크가 자신이 하는 일을 잘못 표현할 때 그 차이가 드러납니다.

빌드 진행 상황

12개 단계, 4개 페이즈. 각 단계마다 하나의 브랜치와 하나의 스쿼시 병합 PR이 있어, 히스토리를 읽으면 실제 빌드가 진행된 과정이 그대로 보입니다.

페이즈

단계

상태

A · 기반

0 · 원시 호출 한 번

🔨 진행 중

A · 기반

1 · 픽스처 데이터

⬜

A · 기반

2 · 결정론적 코어

⬜

B · 에이전트

3 · 첫 도구 루프

⬜

B · 에이전트

4 · 변동 측정

⬜

B · 에이전트

5 · 평가 하네스

⬜ v0.5

C · 완성도 높이기

6 · 복구 루프

⬜

C · 완성도 높이기

7 · 적대적 케이스

⬜

C · 완성도 높이기

8 · 추적

⬜

C · 완성도 높이기

9 · 검색

⬜ v0.9

D · 출시

10 · 실제 데이터

⬜

D · 출시

11 · MCP 서버

⬜

D · 출시

12 · 사이트 및 문서

⬜ v1.0

실행하기

uv가 필요합니다. uv가 Python 자체를 설치하고 관리하므로 유일한 사전 요구 사항입니다.

winget install --id=astral-sh.uv -e

그런 다음 저장소 루트에서:

uv sync

그러면 가상 환경이 생성되고, .python-version에 고정된 Python 버전이 설치되며, 프로젝트가 설치됩니다.

Google AI Studio에서 무료 Gemini API 키를 추가하세요:

copy .env.example .env

GEMINI_API_KEY= 뒤에 키를 붙여넣으세요. 그런 다음 무료 티어 카탈로그가 예고 없이 변경되므로, 키로 접근할 수 있는 모델을 확인하세요:

uv run scripts/raw_call.py --list

그 ID 중 하나를 .env에 GEMINI_MODEL로 넣고 호출하세요:

uv run scripts/raw_call.py

답변을 추출하기 전에 전체 요청과 전체 응답을 출력합니다. 이것이 스크립트의 핵심입니다. 프레임워크가 숨기기 전에 그 형태를 알아 둘 가치가 있습니다.

계정의 실제 rate limit은 aistudio.google.com/rate-limit에서 확인할 수 있습니다. Google은 더 이상 API 문서에 모델별 무료 티어 표를 게시하지 않습니다.

평가

평가 스위트는 5단계에서 도입됩니다. 케이스는 직접 작성되며 절대 생성되지 않습니다 — 같은 도구가 코드와 채점을 모두 작성한다면 점수는 의미가 없습니다. 점수는 반복 실행에 걸친 통과 비율로 보고됩니다. 비결정적 시스템에서의 단일 통과는 일화에 불과하기 때문입니다.

프롬프트, 도구 설명 또는 모델을 변경하는 모든 커밋은 커밋 메시지에 평가 전후 점수를 포함해야 합니다.

라이선스

MIT. LICENSE를 참조하세요.

David Koen이 제작했습니다.

Available Tools

4 tools
check_feasibilityA
Read-only

Check whether a playlist brief is possible at all, before choosing any tracks.

Call this first, before query_library, whenever the user asks for a playlist with constraints. If feasible is false the brief cannot be satisfied by this library: tell the user which constraint fails and stop. Do not build a playlist anyway, do not relax the constraint yourself, and do not try alternative track combinations.

max_achievable_display is the longest playlist the library can produce under these constraints. When explaining a refusal, describe what the user asked for in their own words and quote max_achievable_display for what is possible. Never show the user a millisecond figure and never convert one yourself.

A brief with no constraints at all cannot be checked. If the user has not said how long the playlist should be or what should be in it, ask them before calling this.

NameRequiredDescriptionDefault
released_afterNo
released_beforeNo
required_genresNo
max_track_duration_msNo
max_tracks_per_artistNo
min_total_duration_msNo

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare a safe read-only, closed-world operation, and the description goes well beyond them: it discloses the 'feasible' boolean and 'max_achievable_display' return semantics, plus a refusal protocol and a presentation rule (never surface or convert millisecond figures). That is real behavioral context an agent could not infer from readOnlyHint alone.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose and the sequencing rule, and most sentences add distinct policy. The refusal paragraph slightly over-enumerates ('do not build a playlist anyway, do not relax the constraint yourself, and do not try alternative track combinations'), which are three phrasings of one instruction.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a six-parameter, no-output-schema tool the usage and refusal policy are complete, but the description omits any explanation of the parameter formats an agent must supply, which is the main remaining risk. It explains two return fields but not the full result shape.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden for six parameters, yet it never explains any of them. It is ambiguous whether released_after/released_before are years or epoch timestamps, and units for max_track_duration_ms and min_total_duration_ms are only obliquely implied by the millisecond warning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('check whether a playlist brief is possible') and positions it temporally ahead of track selection, which distinguishes it from query_library and validate_playlist. An agent knows immediately that this is a pre-flight gate, not a builder or validator.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-call ('first, before query_library, whenever the user asks for a playlist with constraints'), explicit when-not ('a brief with no constraints at all cannot be checked — ask them before calling'), and explicit failure behavior ('tell the user which constraint fails and stop', 'do not relax the constraint yourself'). Alternatives and exclusions are both covered.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

enrich_artistA
Read-only

Look up background on one artist in the user's library, from published sources.

Call this for any question about an artist's history, members, origins, influences or career. Pass the artist's name and the user's question. It returns the passages from MusicBrainz and Wikipedia that best match the question.

Answer only from these passages. If they do not contain the answer, tell the user your sources do not cover it, even if you believe you know. Never fill the gap from your own knowledge.

Cite every passage you use by its id in square brackets, such as [can-03], straight after the claim it supports. Never cite an id this tool did not return.

This tool knows nothing about the user's tracks. Use query_library for those.

NameRequiredDescriptionDefault
artistYes
questionYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the readOnly/openWorld annotations by disclosing the data sources (MusicBrainz, Wikipedia), the grounding contract (answer only from returned passages, refuse otherwise), and the citation protocol required in the reply. These are response-shaping rules the annotations cannot convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then usage, then output contract, then sibling routing. Every sentence adds an actionable rule; nothing is redundant with the schema or annotations.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, and the description supplies the return shape ('passages ... that best match the question') along with the id format used in citations. An agent has everything needed to call it and to post-process the result correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate, and it does: 'artist' is clarified as the name and 'question' as the user's question, which implies phrasing the question in natural language rather than keywords. It stops short of format examples or naming conventions for the artist string.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('look up background on one artist ... from published sources') and names the sources. The final sentence explicitly separates it from query_library, so an agent can route without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit trigger ('any question about an artist's history, members, origins, influences or career') and an explicit exclusion plus alternative ('This tool knows nothing about the user's tracks. Use query_library for those.'). Both when and when-not are covered.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_libraryA
Read-only

Search the user's music library and return matching tracks with their totals.

Call this before stating any fact about the library. Never estimate a count, a duration or a track name from your own knowledge, and never add numbers up yourself: use total_matching and total_duration_ms from the result, which are computed exactly. The tracks list is truncated to limit entries, so it may be shorter than total_matching.

To find a longest, shortest, highest or lowest track, pass order_by as one of longest, shortest, highest_energy, lowest_energy and read the first entry. Never rank, compare or pick a maximum out of the list yourself; the ordering is computed here.

Quote duration_display for a track and total_duration_display for a set of them. Never convert milliseconds to minutes yourself.

Energy is an estimate, not a measurement. Every track carries energy_provenance saying where its value came from, and any answer that mentions energy must say it is an estimate.

If a genre search finds nothing, known_genres lists the genre tags the library actually uses. If the user's word is another name for one of them, as rap is for hip hop, search again with that tag and say which tag you searched. Never substitute a genre that is merely related.

NameRequiredDescriptionDefault
genreNo
limitNo
artistNo
playedNo
order_byNo
released_afterNo
max_duration_msNo
min_duration_msNo
released_beforeNo

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish read-only and non-destructive behavior, but the description adds substantial behavioral context beyond that: the tracks list is truncated to limit, totals are computed exactly, ordering is computed server-side, energy is an estimate with provenance, and genre fallback is available. These details materially change how an agent should interpret and present results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core action and key rules, and most sentences carry actionable guidance. It is somewhat long and repeats the same anti-estimation theme across multiple sentences, which costs some conciseness, but the structure remains purposeful.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description appropriately explains key return fields such as total_matching, total_duration_ms, tracks, duration_display, and energy_provenance. However, for a 9-parameter tool with 0% schema description coverage, it leaves important input filters undocumented, so the overall context is only partially complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and there are 9 parameters, so the description must carry the burden of explaining them. It adds meaning for order_by values, genre fallback behavior, and limit truncation, but it does not explain artist, played, released_after, released_before, min_duration_ms, or max_duration_ms, leaving more than half of the parameters undocumented.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: search the user's music library and return matching tracks with totals. It is immediately distinguishable from the sibling tools validate_playlist, enrich_artist, and check_feasibility, which serve different purposes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit usage rules: call before stating any fact about the library, never estimate counts or durations, use total_matching and total_duration_ms, and pass order_by for longest/shortest/highest/lowest queries. It also explains the genre fallback path and when to search again with known_genres. The guidance is unusually complete for an agent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

validate_playlistA
Read-only

Check a proposed playlist against hard constraints and report every violation.

Call this before presenting any playlist to the user. Do not judge for yourself whether a playlist meets its constraints. Each violation names the offending track ids and an adjust_by amount in the constraint's own unit: negative means remove that much, positive means add that much. Act on adjust_by rather than guessing how much to change.

When describing the playlist, quote track_count and total_duration_display from this result verbatim. Never sum durations yourself and never convert milliseconds to minutes yourself.

A duration violation also carries adjust_by_display, the same amount written in minutes and seconds. Show the user that, never the millisecond figure.

Pass the same release-year bounds you gave check_feasibility, so a track outside the window is caught here too.

If the user asks for an energy level or a mood, pass target_energy between 0 and 1. It is scored, never enforced: soft_scores reports how close the playlist came, and because energy is estimated rather than measured, any answer that mentions it must say so.

NameRequiredDescriptionDefault
track_idsYes
target_energyNo
released_afterNo
released_beforeNo
required_genresNo
max_total_duration_msNo
max_track_duration_msNo
max_tracks_per_artistNo
min_total_duration_msNo

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish that this is a safe read (readOnlyHint=true, destructiveHint=false), and the description adds substantive behavior the annotations cannot convey: violation records carry track ids and a signed adjust_by in the constraint's unit, duration violations also carry adjust_by_display, and target_energy is scored rather than enforced. It also warns that energy is estimated and must be disclosed, which is a real behavioral constraint on downstream answers.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The most important directive (what the tool does, and call it before presenting) is front-loaded, and every subsequent paragraph carries a distinct instruction: adjust_by semantics, verbatim quoting rules, cross-tool bound reuse, and energy caveat. It is on the long side for a nine-parameter tool, but no sentence is redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description has to sketch the return shape and it does: violations, adjust_by, adjust_by_display, soft_scores, track_count, total_duration_display. The remaining gap is the unmentioned constraint parameters, which an agent must guess from their names alone.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden. It explains target_energy well (0-1 range, scored not enforced, soft_scores output) and gives intent for released_after/released_before by tying them to check_feasibility, but required_genres, max_total_duration_ms versus min_total_duration_ms, max_track_duration_ms, and max_tracks_per_artist are never mentioned — half the parameters remain opaque.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (check a proposed playlist against hard constraints) plus the scope of the output (report every violation). It implicitly separates itself from check_feasibility by instructing the caller to reuse the release-year bounds given to that sibling, so an agent can tell the two apart.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit temporal gating ('Call this before presenting any playlist', 'Do not judge for yourself'), an explicit cross-tool dependency ('Pass the same release-year bounds you gave check_feasibility'), and an explicit conditional trigger ('If the user asks for an energy level or a mood, pass target_energy'). Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observedcheck_feasibility
    • First observedenrich_artist
    • First observedquery_library
    • First observedvalidate_playlist

TDQS

A4.5/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct role: check_feasibility checks a brief before track selection, query_library searches local tracks, validate_playlist checks a proposed playlist, and enrich_artist retrieves external artist background. The descriptions explicitly distinguish the two check-style tools by workflow phase and data source.

Naming Consistency5/5

All four names follow a consistent snake_case verb_noun pattern: validate_playlist, enrich_artist, check_feasibility, query_library. The verbs are distinct and predictable for their actions.

Tool Count5/5

Four tools is well-scoped for a focused playlist-assistant server, with each tool covering a separate workflow stage. There is no redundant or filler tool.

Completeness4/5

The set covers feasibility checking, library search, playlist validation, and artist enrichment, which supports the core playlist-building workflow. Minor gaps remain around explicitly creating, saving, or exporting a playlist, but agents can work around this by presenting validated playlists.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers