빈자의 에이전틱 코딩, 토큰 모아 태산
카카오의 은혜를 입어 쓰던 ChatGPT Pro 플랜이 끝난 뒤로 저는 Astra를 잠깐만 써도 1주일치 사용량이 날아가는 삶을 살고 있습니다. 토큰은 비싸고 사람은 저렴한 것이 오늘의 우리네 현실인 것입니다. 그러니 토큰 값을 아끼려면 사람이 머리를 더 써야 합니다.
그래서 오늘의 주제는, 토큰을 아주 조금 아끼는 방법 한 가지입니다.
이런 코드가 있다고 해 봅시다.
import { Input } from '@/components/ui/input'
import { createFileRoute } from '@tanstack/react-router'
import { useState } from 'react'
export const Route = createFileRoute('/demo')({
component: RouteComponent,
})
type BookData = {
title: string
author_name?: string[]
}
async function fetchData(title: string): Promise<{
docs: BookData[]
}> {
const url = `https://openlibrary.org/search.json?q=title:${title}`
const response = await fetch(url)
const data = await response.json()
return data
}
function RouteComponent() {
const [data, setData] = useState<BookData[]>([])
return <div>
<div className="p-8 flex flex-col gap-4 items-center">
<h1 className="text-2xl font-bold">Book Search</h1>
<Input placeholder="Enter book title" onChange={async (e) => {
const title = e.target.value
const result = await fetchData(title)
if (!title) {
setData([])
return
}
setData(result.docs)
}} />
<div className="p-8 flex flex-col gap-4 items-center">
{data.length ? data.map((book, index) => (
<div key={index} className="border p-4 w-full max-w-md">
<h2 className="text-lg font-bold">{book.title}</h2>
<p className="text-sm">Author(s): {book.author_name?.join(', ') ?? 'Unknown Author'}</p>
</div>
)) : <p>No books found</p>}
</div>
</div>
</div>
}
책 제목을 입력하면 실시간으로 Open Library 검색 결과를 보여주는 예제인데, 대충 이런 모양이 되죠.

샬롯의 거미줄이 제일 위에 뜨는 걸 보니 역시 거미는 웹 개발자인 모양이네요.
그런데 이대로라면 사용자가 input 필드에 있는 값을 바꿀 때마다 API에 요청을 보내게 되고, 요청이 너무 많을 뿐 아니라 아래쪽의 목록이 휙휙 바뀌는 게 보기 좋지 않습니다. 이럴 때는 보통 debouncing이라는 기법을 적용하게 됩니다. 이걸 에이전트에게 고쳐 달라고 할 때, 대부분은 이런 식으로 요청할 거예요.
- “
/src/routes/demo.tsx에 debounce를 구현해 줘.”
//...
async function fetchData(title: string, signal: AbortSignal): Promise<{
docs: BookData[]
}> {
const url = `https://openlibrary.org/search.json?q=${encodeURIComponent(`title:${title}`)}`
const response = await fetch(url, { signal })
const data = await response.json()
return data
}
function RouteComponent() {
const [data, setData] = useState<BookData[]>([])
const [title, setTitle] = useState('')
useEffect(() => {
if (!title.trim()) {
setData([])
return
}
const controller = new AbortController()
const timeout = window.setTimeout(async () => {
try {
const result = await fetchData(title.trim(), controller.signal)
setData(result.docs)
} catch (error) {
if (!controller.signal.aborted) {
console.error('Book search failed', error)
setData([])
}
}
}, 300)
return () => {
window.clearTimeout(timeout)
controller.abort()
}
}, [title])
//...

Route component 안에 debounce를 구현해 주네요. 입력 토큰 137K, 출력 토큰 1.3K, 캐싱된 토큰을 제외한 전체 사용량은 42.5K입니다. 재사용 불가능한 코드인 게 마음에 들지 않지만, 어쨌든 잘 작동합니다.
하지만 인간지능을 조금만 더 써서 에이전트가 쓸 토큰을 줄여 봅시다. 물론 debounce를 직접 작성해도 되지만 남의 코드를 가져오면 훨씬 빠르죠. @uidotdev/usehooks에 useDebounce가 포함되어 있으니 해당 패키지를 설치한 다음 이렇게 요청해 봅시다.
- “
/src/routes/demo.tsx의 input에@uidotdev/usehooks의useDebounce를 300ms delay로 적용해 줘.”
//...
function RouteComponent() {
const [data, setData] = useState<BookData[]>([])
const [title, setTitle] = useState('')
const debouncedTitle = useDebounce(title, 300)
useEffect(() => {
if (!debouncedTitle) {
setData([])
return
}
let cancelled = false
fetchData(debouncedTitle).then((result) => {
if (!cancelled) setData(result.docs)
})
return () => {
cancelled = true
}
}, [debouncedTitle])
return <div>
<div className="p-8 flex flex-col gap-4 items-center">
<h1 className="text-2xl font-bold">Book Search</h1>
<Input placeholder="Enter book title" value={title} onChange={(e) => setTitle(e.target.value)} />
//...

입력 토큰 112K, 출력 토큰 1.01K, 총 사용량 34.7K로 줄어들었습니다. 대충 20% 정도 더 저렴해졌다고 보면 되겠네요. 이 사용량에는 package.json을 열어서 useHooks가 설치되어 있는지 확인하는 부분도 들어 있기 때문에, 같은 컨텍스트 내에서 동일한 구현을 여러 번 반복한다면 useDebounce를 재사용하는 비용은 더욱 줄어들 거예요.
여기에서는 간단한 패턴을 예시로 들었지만, 작성해야 하는 코드가 복잡해질수록 외부 라이브러리를 사용하는 것이 훨씬 저렴해집니다. 에이전트가 CSS를 한땀한땀 깎는 것보다 @shadcn/ui를 사용하면 훨씬 저렴해지는 식이죠.
left-pad 사건을 기억하시나요? 그 때는 왜 내로라하는 빅테크 회사들이 다들 겨우 문자열 앞에 들여쓰기를 하는 11줄짜리 코드를 외부 디펜던시로 가져다 쓰다가 그 사단이 났는지 의문이었는데, 이제는 이해할 수 있게 되었습니다. 하루에 코드를 열 줄이라도 덜 쓰면 토큰 모아 태산을 만들 수 있을지도 몰라요.
Dani Soohan Park (@heartade)
Follow this blog at Fediverse: @heartade@blog.heartade.dev
Follow my shorter shoutouts at Fediverse: @heartade@social.silicon.moe
Follow me at Bluesky: @heartade.dev