Section 1디자인 원칙
우리는 picoagents라는 Python 라이브러리를 처음부터 직접 만든다. 효과적인 에이전트 인터페이스는 최소한이면서도 완전해야 한다 — Agent(model=.., tools=[..], memory=..)는 정확히 세 능력을 제공한다. 모델을 통한 추론, 툴을 통한 행동, 메모리를 통한 적응.
from picoagents import Agent, OpenAIChatCompletionClient def get_weather(location: str) -> str: """Get current weather for a given location.""" return f"The weather in {location} is sunny, 75°F" agent = Agent( name="assistant", instructions="You are helpful. Use tools when appropriate.", model_client=OpenAIChatCompletionClient(model="gpt-4.1-mini"), tools=[get_weather] # 함수가 자동으로 툴이 된다 ) async for event in agent.run_stream("What's the weather in Paris?"): print(event)
다섯 가지 아키텍처 원칙
| 원칙 | 이유 |
|---|---|
| 비동기 우선 | LLM 호출(500ms~5초)·툴·I/O가 느리다. 동기 3-에이전트 워크플로우 30초가 동시성으로 10초가 된다. |
| 이벤트 기반 스트리밍 | 30초 넘는 과업에서 빈 화면을 피한다. 실시간 진행·디버깅 관측 가능성 제공. |
| 컴포넌트 직렬화 | 모든 컴포넌트가 JSON 직렬화 가능 — 버전 관리, 구성 UI, 세션 복원. |
| 우아한 취소 | 잘못된 프롬프트·무한 루프·피드백 제공 시 장시간 작업을 중도 중단. |
| 추상 기반 클래스 | 벤더 종속 방지, 목 구현 테스트. BaseTool은 함수→REST→MCP까지 코드 변경 없이 확장. |
Section 2에이전트 실행 루프
모든 에이전트 프레임워크의 심장은 실행 루프다. 모든 상호작용은 동일한 근본 패턴을 따른다.
- 컨텍스트 준비 — 과업 + 지시문 + 메모리 + 대화 기록을 결합한다.
- 모델 호출 — 컨텍스트를 LLM에 보내고 응답을 받는다.
- 응답 처리 — 텍스트 응답을 처리하거나 툴 호출을 실행한다.
- 반복 — 툴이 호출되었다면 결과를 컨텍스트에 추가하고 2단계부터 반복.
- 반환 — 최종 응답을 제공하고 메모리를 갱신한다.
async def agent_execution_loop(task): context = prepare_context(task, instructions, memory, history) while not done: response = await model_client.create(context) if response.has_tool_calls: for tool_call in response.tool_calls: result = await execute_tool(tool_call) context.append(result) else: done = True update_memory(context) # optional return response
BaseAgent는 추상 기반 클래스로, 모든 에이전트는 수집형 run()과 스트리밍형 run_stream() 두 모드를 구현해야 한다. 과업 취소는 CancellationToken — 비동기 작업 전반에 중단 신호를 전파하는 스레드 안전 메커니즘 — 으로 구현한다. cancel()이 호출되면 스트리밍 LLM 호출을 중단하고, 새 툴 실행을 막으며, asyncio.CancelledError를 발생시킨다.
Section 3모델 클라이언트와 구조화된 출력
모델 클라이언트는 에이전트가 생성형 AI 모델과 마주하는 인터페이스다. BaseChatCompletionClient는 두 핵심 메서드를 정의한다 — create(생성)와 create_stream(스트리밍). 패턴은 언제나 같다 — 우리 타입을 제공자 형식으로 변환, API 호출, 응답을 다시 통합 형식으로 변환.
구조화된 출력은 LLM이 자유 형식 텍스트가 아니라 미리 정의된 스키마(Pydantic 모델)를 따르는 응답을 내놓도록 강제한다. 이것이 신뢰할 수 있는 툴 요청을 가능하게 한다 — 에이전트가 일관되게 같은 구조를 만들어 내므로 우리는 안전하게 툴을 실행할 수 있다. 7장의 계획 기반 오케스트레이션에서 output_format 파라미터가 예측 불가능한 LLM 텍스트를 신뢰할 수 있는 데이터 구조로 탈바꿈시키는 것도 같은 원리다.
output_format 지정 시 Pydantic 객체로 보장됨
Section 4툴 추가하기
LLM에서의 함수 호출(툴 호출)은 모델이 어떤 함수를, 어떤 인자로 호출할지를 결정하게 한다. 툴은 두 갈래로 나뉜다 — 범용 툴(폭넓은 역량)과 작업 특화 툴(특정 과업).
BaseTool 클래스는 모든 툴의 공통 인터페이스다. FunctionTool은 개발자 친화적 — 평범한 Python 함수를 그대로 툴로 변환한다. 시그니처와 docstring에서 자동으로 파라미터 스키마를 추출한다.
# 에이전트가 이 구조를 안정적으로 생성한다 { "tool": "get_weather", "arguments": { "location": "Paris" } } # 이제 우리는 안전하게 툴을 실행할 수 있다 result = await execute_tool(tool_call)
툴로서의 에이전트
에이전트 자체도 다른 에이전트의 툴이 될 수 있다(4.11절). 결과 정보성(Result Informativeness)의 제어가 중요하다 — 하위 에이전트가 호출자에게 얼마나 풍부한 정보를 돌려줄지 조절한다.
Section 5메모리 더하기
에이전트가 시간이 지나며 향상되려면 메모리 — 과거 상호작용에서 정보를 회상·재활용하는 능력 — 가 필요하다.
| 유형 | 설명 |
|---|---|
| 단기 메모리 | 메시지 기록을 통한 현재 과업 작업 메모리 |
| 장기 메모리 | 검색 증강 생성(RAG)을 통한 세션 횡단 지식 |
| 애플리케이션 관리 | 개발자가 통제 — 사전 정의 전략으로 자동 저장·검색 |
| 에이전트 관리 | 에이전트가 툴을 통해 무엇을·언제 저장할지 직접 결정 |
BaseMemory 인터페이스가 메모리 백엔드를 추상화한다. 에이전트 관리 메모리는 메모리를 툴로 노출해 에이전트가 세션을 가로질러 학습하게 하며, 메모리 연산(view·create·search·append·str_replace)을 제공한다. 다만 에이전트가 자신의 지식을 통제할 때는 보안 고려가 필수다.
Section 6미들웨어와 컨텍스트 엔지니어링
미들웨어는 제어와 관측 가능성을 위한 검사 계층이다. BaseMiddleware 인터페이스는 세 후크를 제공한다 — process_request(), process_response(), process_error(). 입력 검증을 위한 SecurityMiddleware, 관측 가능성을 위한 LoggingMiddleware, 리소스 제어를 위한 RateLimitMiddleware가 구체적 예다.
OTelMiddleware는 에이전트 실행을 OpenTelemetry 트레이스로 자동 계측한다. Jaeger 같은 도구로 시각화할 수 있으며, 관측 가능성 플랫폼과 통합된다. 프라이버시를 위해 콘텐츠 캡처를 선택적으로 제어한다.
컨텍스트 엔지니어링
장기 실행 과업은 두 가지 문제에 부딪힌다 — 컨텍스트 부식(Context Rot)(긴 컨텍스트에서 성능 저하)과 컨텍스트 폭발(Explosion)(컨텍스트가 윈도우를 넘침). 관리 전략 — 압축(compaction), 요약, 격리, 선택적 컨텍스트 제공. 루프 수준 훅(Loop-Level Hooks)은 실행 루프의 매 반복에 개입해 컨텍스트를 다듬는다.
휴먼 인 더 루프
툴 승인 요청(Tool Approval Requests) — 고위험 툴 호출 전에 사람의 승인을 요구한다. 무상태 컨텍스트와 인간 입력을 결합해, 에이전트가 사람을 기다리는 동안에도 세션 상태를 보존한다.