메뉴
HN
Hacker News 49일 전

기본 AI 에이전트 만들기: 장기 작업 계획

IMP
8/10
핵심 요약

단순한 질의응답에 머무는 LLM의 한계를 극복하고, 복잡하고 긴 작업을 독립적으로 수행할 수 있는 AI 에이전트의 '장기 작업 계획(Long Task Planning)' 능력을 구현하는 방법을 다룹니다. 이를 위해 에이전트가 목표를 이해하고 작업을 체계적으로 분해 및 관리할 수 있도록 돕는 '스크래치패드(Scratchpad)'와 '할 일 목록(To-do list)'라는 두 가지 핵심 도구가 소개됩니다.

번역된 본문

밑바닥부터 기본 AI 에이전트 만들기: 장기 작업 계획(Long Task Planning) Roger Oriol · 읽는 데 11분 · 2일 전

이전 글에서는 에이전트가 자율적으로 작동할 수 있도록 핵심적인 도구들을 추가했습니다. 파일 찾기, 파일 읽기 및 쓰기, bash 명령어 실행, 웹에서 콘텐츠 가져오기 등의 기능을 부여했습니다. 이 몇 가지 도구만으로도 매우 유능한 에이전트를 만들 수 있었습니다.

그렇다면 에이전트가 길고 복잡한 작업을 실행할 때는 어떤 일이 발생할까요? 현재 에이전트는 매우 잘 작동하지만, 에이전트가 더 많은 작업을 해결하려면 오랜 시간 동안 작업에 집중할 수 있어야 합니다. 하지만 지금 상태로는 길고 복잡한 작업을 에이전트에 맡기면 장기적인 계획을 세우지 못하고, 아주 작은 진척만 이루어도 작동을 멈춘다는 것을 알 수 있습니다. 이는 LLM(대형 언어 모델)이 기본적으로 대화형으로 작동하도록 훈련되었기 때문에 당연한 결과입니다. LLM은 질문과 대답을 주고받는 형태를 기대합니다. 이는 단순한 챗봇에게는 적합하지만, 우리의 에이전트는 요청을 받은 후 결과를 반환하기 전까지 오랜 시간 스스로 작업을 수행할 수 있어야 합니다.

장기 작업 계획 (Long Task Planning) 다음으로 우리 에이전트에 추가할 능력은 길고 복잡한 작업을 계획하는 능력입니다. 에이전트에 필요한 구체적인 능력은 다음과 같습니다:

  • 작업의 목표 이해
  • 작업에 착수하기 전에 작업 해결 방법 미리 계획
  • 작업을 구체적인 단계로 세분화
  • 대기 중(pending), 진행 중(in progress), 완료된(completed) 작업 추적
  • 현재 계획에 문제가 발생할 경우 접근 방식 재고려
  • 작업을 중단하기 전에 계획된 모든 것이 실제로 완료되었는지 확인

에이전트에 이러한 능력을 부여하기 위해 우리는 이전 글에서 추가했던 '도구(Tools)'를 활용할 것입니다. 또한 모델의 시스템 프롬프트(System Prompt)에 장기 작업 계획을 사용하는 방법을 설명할 것입니다.

새로운 도구: 스크래치패드 (Scratchpad) 이 도구는 매우 단순하지만 강력합니다. 모델이 자신의 생각을 적어두고 나중에 다시 읽을 수 있는 공간을 제공하는 것입니다. 이 도구의 주요 이점은 모델이 본격적으로 작업을 시작하기 전에 목표를 완전히 숙지하고 전체적인 접근 방식을 계획하도록 강제한다는 것입니다. 이 도구는 스크래치패드의 콘텐츠를 파일이나 데이터베이스 대신 메모리에 저장합니다. 세션 간에 스크래치패드 콘텐츠를 공유할 필요가 없으므로 이 방식은 적절합니다.

다음은 파이썬(Python) 구현 코드입니다: class Scratchpad: """메모리 내 스크래치패드에서 읽고 쓰기""" def init(self): self._content = ""

def read(self) -> str:
    if self._content == "":
        return "(empty)"
    return self._content

def write(self, content: str) -> str:
    self._content = str(content).strip()
    return self._content

scratchpad = Scratchpad()

def read_scratchpad(): """스크래치패드 내용 읽기""" return scratchpad.read()

def write_scratchpad(content: str): """ 스크래치패드에 쓰기. 기존 내용은 덮어씌워집니다. """ scratchpad.write(content) return "Successfully written content into scratchpad"

이 코드는 이 블로그 시리즈의 Github 저장소에서 찾아 복제할 수 있습니다.

새로운 도구: 할 일 목록 (To-do list) 할 일 목록은 에이전트가 작업을 세분화하고, 남은 할 일(pending), 현재 진행 중인 작업(in progress), 이미 완료된 작업(done)을 추적할 수 있게 해줍니다. 이 도구는 몇 가지 바람직한 개발 관행도 강제합니다. 여러 작업이 동시에 진행 중인 상태를 허용하지 않고, 유효하지 않은 작업 상태나 중복된 작업을 허용하지 않습니다. 스크래치패드와 마찬가지로 이 도구는 할 일 목록을 파일이나 데이터베이스 대신 메모리에 저장합니다. 에이전트 세션 간에 할 일 목록을 공유할 필요가 없으므로 이 방식 역시 적절합니다.

RETRY_LIMIT = 3

class ToDoList: """메모리에 할 일 목록을 보관하는 헬퍼 클래스""" statuses = ["pending", "in_progress", "done", "cancelled", "failed"]

def __init__(self):
    self._items = []

def read(self, include_completed=False):
    """할 일 목록 읽기"""
    if include_completed:
        return [item.copy() for item in self._items]
    else:
        return [item.copy() for item in self._items if item["status"] != "done" and item["status"] != "cancelled"]

def append(self, id, content, st
원문 보기
원문 보기 (영어)
Build A Basic AI Agent From Scratch: Long Task Planning Roger Oriol 11 min read · 2 days ago -- Listen Share In the previous part of the Build A Basic AI Agent From Scratch series, we added the essential tools to our agent to allow it to work autonomously for us. We gave it the ability to find files, read and write files, run bash commands and get content from the web. We got a very capable agent with just these tools. What happens when the agent runs long and complex tasks? The current agent works very well, but we want our agent to get a lot of work done, and this requires staying on the task for long spans of time. Right now, if we try to give our agent long and complex tasks we will find that it does not think long term, and it stops working after the littlest progress. This is to be expected because the LLM is trained to behave conversationally. It expects to go back and forth in a question-answer basis. This is fine for a simple chatbot, but our agent needs to be able to get a request and work for a long time on it before returning a result. Long task planning The next ability we will give to our agent is the ability to plan for long and complex tasks. The abilities our agent needs are: Understand the goal of the task Plan how to tackle the task beforehand Break the task into concrete steps Keep track of pending, in progress and completed tasks If something goes wrong with the current plan, rethink the approach Check that everything planned is actually done before stopping To give our agent these abilities, we will rely on the last part’s addition: tools . We will also explain the model how to use long task planning in the model’s system prompt . New tool: Scratchpad This is a very simple but powerful tool. We are just giving the model a place to write it’s thoughts and read them again at a later time. The main benefit of this tool is that it forces the model to think through the goal and plan the whole approach before starting working on it. The tool saves the scratchpad content into memory instead of a file or database, which is fine because we don’t want to share the scratchpad content between sessions. Here’s the python implementation: class Scratchpad: """Read and write from a in-memory scratchpad""" def __init__(self): self._content = "" def read(self) -> str: if self._content == "": return "(empty)" return self._content def write(self, content: str) -> str: self._content = str(content).strip() return self._content scratchpad = Scratchpad() def read_scratchpad(): """Read the contents of the scratchpad""" return scratchpad.read() def write_scratchpad(content: str): """ Write into the scratchpad. The previous content will be overwritten. """ scratchpad.write(content) return "Successfully written content into scratchpad" You can find and clone this code in this blog series&#x27; <a href="https://github.com/rogiia/basic-agent-harness" target="_blank">Github repo</a>. New tool: To-do list A to-do list allows the agent to decompose the work into tasks and keep track of them to know what’s left to do ( pending ), what it’s working on currently ( in progress ) and what is already done ( done ). This tool also enforces some good practices: it doesn’t allow multiple tasks to be in progress at the same time, it doesn’t allow invalid task statuses and it doesn’t allow repeated tasks. Just like the scratchpad, this tool saves the to do list into memory instead of a file or database. This is also fine because we don’t want to share the to-do list between agent sessions. RETRY_LIMIT = 3 class ToDoList: """Helper class to hold a to-do list in memory""" statuses = ["pending", "in_progress", "done", "cancelled", "failed"] def __init__(self): self._items = [] def read(self, include_completed=False): """Read the to-do list""" if include_completed: return [item.copy() for item in self._items] else: return [item.copy() for item in self._items if item["status"] != "done" and item["status"] != "cancelled"] def append(self, id, content, status): if status not in ToDoList.statuses: raise Exception(f"Invalid status {status}. " "Valid to-do statuses: pending, in_progress, done, " "cancelled, failed") if self.contains(id): raise Exception(f"To do item {id} already exists!") new_item = {"id": id, "content": content, "status": status, "retries": 0} self._items.append(new_item) return new_item.copy() def contains(self, id) -> bool: """Check if the to do list contains an item with a specific id""" for item in self._items: if item["id"] == id: return True return False def update(self, id, content, status): if status is not None and status not in ToDoList.statuses: raise Exception(f"Invalid status {status}. " "Valid to-do statuses: pending, in_progress, done, " "cancelled, failed") idx = 0 while idx < len(self._items): if self._items[idx]["id"] == id: if content is not None: self._items[idx]["content"] = content if status is not None: prev_status = self._items[idx]["status"] self._items[idx]["status"] = status # A failed task being set back to in_progress is a retry attempt. if prev_status == "failed" and status == "in_progress": self._items[idx]["retries"] += 1 return self._items[idx].copy() idx += 1 raise Exception(f"To do item with id {id} not found") todo_store = ToDoList() def todo_append(id, content, status) -> str: """Append a new to do item to the to do list""" id_str = str(id) content_str = str(content) status_str = str(status) try: todo_store.append(id_str, content_str, status_str) return f"Successfully appended to do item {id_str} in to do list!" except Exception as e: return f"Failed to append to do item: {e}" def todo_list(include_completed=False) -> str: """List all the items in the to do list""" items = todo_store.read(include_completed) result = f"To Do List ({len(items)} items)\n" for status in ToDoList.statuses: count = sum(1 for i in items if i["status"] == status) result += f"{count} {status} items\n" result += "-----\n" for item in items: retry_note = f", {item[&#x27;retries&#x27;] } retries" if item["retries"] > 0 else "" result += f"- [{item[&#x27;id&#x27;]}] {item[&#x27;content&#x27;] } ({item[&#x27;status&#x27;]}{retry_note})\n" return result def todo_update(id, content=None, status=None) -> str: if content is None and status is None: return "No content or status was given to update. Nothing to do." try: item = todo_store.update(id, content, status) retries = item["retries"] if item["status"] == "in_progress" and retries > 0: if retries >= RETRY_LIMIT: return ( f"Updated to do item {id} to in_progress - " f"but this is retry {retries} of { RETRY_LIMIT} (retry limit reached). " f"Do not retry again. Escalate to the user instead." ) return ( f"Successfully updated to do item {id}! " f"Retry attempt {retries} of {RETRY_LIMIT}." ) return f"Successfully updated to do item {id}!" except Exception as e: return f"Failed to update to do item {id}: {e}" New system prompt All the strategies for long term task planning that cannot be implemented into tools are explained to the model in the system prompt. Here we will explain to the model how to plan using the process explained in the beginning of the article, and also how to use the new tools to help it in the planning process. For more details, read the system prompt below. I also added to the system prompt a little comment explaining to the model that if not stated otherwise, the project it has to work on is in the current directory. { "role": "system", "content": ( "You are a capable coding and research assistant.\n\n" "## Available tools\n\n" "Action tools: read_file, write_file, edit_file, glob_files, grep, run_bash, webfetch\n\n" "Planning tools:\n" "- Scratchpad (read_scratchpad / write_scratchpad): your private working memory. " "Use it to think through an approach, store intermediate findings, or draft content " "before committing. Each write fully replaces the previous content.\n" "- To-do list (todo_append / todo_list / todo_update): a persistent task tracker. " "Items carry a status: pending, in_