Skip to content

Agent 아키텍처

truloop-assistant의 pydantic-ai Agent 기반 아키텍처, 요청 처리 흐름, Service Layer 설계를 설명합니다.


아키텍처 다이어그램


Agent 패턴

Singleton Agent

Agent는 애플리케이션 수명 동안 단일 인스턴스로 유지됩니다:

python
# Lazy singleton with async lock
_agent: Agent[AgentDeps, AgentOutput | DeferredToolRequests] | None = None
_agent_lock = asyncio.Lock()

async def get_agent() -> Agent[AgentDeps, AgentOutput | DeferredToolRequests]:
    global _agent
    if _agent is not None:
        return _agent
    async with _agent_lock:  # Double-checked locking
        if _agent is None:
            _agent = create_agent()
    return _agent

두 Agent 체계

시스템은 회원 Agent비회원 Agent 2개의 독립 Agent를 운영하는 구조입니다:

주의

미구현: 비회원 Agent는 코드 scaffolding만 존재하며, 프로덕션에서 동작하지 않습니다. SMS two-way 메시징 기반의 비회원 Agent는 향후 구현 예정입니다. 현재 프로덕션에서는 회원 Agent만 운영됩니다.

항목회원 Agent비회원 Agent (미구현)
채널Sendbird 채팅SMS/RCS/WhatsApp
deps_typeAgentDepsGuestAgentDeps
output_typeAgentOutput | DeferredToolRequestsAgentOutput
toolsets9개 (all_toolsets)1개 (guest_toolset)
기능일정 관리, 장소 검색, 메시지 릴레이, RAG, 장기 기억 등가용 시간 수집, 일정 조회
Deferred Tool지원 (캘린더, 권한, 확인 다이얼로그)미지원

회원 Agent 구성 요소

구성 요소설명
ModelOpenRouter (Claude Sonnet 4.6)
deps_typeAgentDeps - 런타임 의존성
output_typeAgentOutput | DeferredToolRequests
instructions정적 기본 프롬프트 (XML 태그 기반)
toolsets9개 도구 세트 (all_toolsets)
dynamic instructionsPer-request 동적 프롬프트 (사용자 컨텍스트, 페르소나)

Instruction 계층

유형시점내용
StaticAgent 초기화 시 1회기본 규칙, 제약사항, 응답 형식
Dynamic: Persona매 요청봇 페르소나 (성격, 말투, 이모지 사용)
Dynamic: User Context매 요청사용자 이름, EID, 현재 시간

AgentDeps (런타임 의존성)

각 Agent 실행에 전달되는 불변(Immutable) 컨텍스트입니다:

python
class AgentDeps(BaseModel):
    user: User                    # 사용자 정보 (eid, username, name, locale, timezone)
    truloop_client: TruloopClient # truloop Core API 클라이언트
    now: datetime                 # 현재 시각 (UTC, default_factory)
    services: AgentServices       # 서비스 컨테이너
    persona: Persona              # 봇 페르소나

AgentServices

python
class AgentServices(BaseModel):
    history_service: MessageHistoryService  # 대화 이력 (Redis)
    sendbird_client: SendbirdClient         # Sendbird 메시지 발송
    bot_user_id: str                        # 봇 사용자 ID
    rag_service: RAGService | None          # RAG 검색 (선택적)
    supermemory_client: AsyncSupermemory | None  # 장기 기억 (선택적)

GuestAgentDeps (비회원 전용)

주의

미구현: 아래 코드 구조는 scaffolding으로 존재하지만, 프로덕션에서 사용되지 않습니다.

python
class GuestAgentDeps(BaseModel):
    guest: GuestContext           # 비회원 정보 (eid, name, phone, locale, loops)
    truloop_client: TruloopClient # truloop Core API 클라이언트
    now: datetime                 # 현재 시각 (UTC)
    services: GuestAgentServices  # 서비스 컨테이너
    persona: Persona              # 봇 페르소나

class GuestAgentServices(BaseModel):
    history_service: MessageHistoryService  # 대화 이력 (Redis)
    messaging_gateway: MessagingGateway     # SMS/RCS/WhatsApp 발송

요청 처리 흐름

Sendbird Webhook 흐름

Tool Message 흐름 (Deferred Tool 완료)


Service Layer 패턴

설계 원칙

Route Handler는 HTTP 관심사만 처리하고, Service가 비즈니스 로직을 담당합니다:

계층책임라인 수
Route Handler요청 파싱, Background task 시작, 즉시 응답25-88줄
ServiceAgent 실행, 외부 서비스 연동, 에러 처리비즈니스 로직 전체

주요 서비스

서비스역할
WebhookProcessorServiceSendbird Webhook 메시지 처리, Agent 실행, 응답 라우팅. Pending deferred tool 자동 취소/복구 포함
ToolMessageProcessorServiceTool 결과 검증, Agent 재개, 응답 전송
EventProcessorServicetruloop-core Internal 이벤트 처리 (봇 선택, 일정 초대, 비회원 참여 등). Discriminated union 패턴 매칭
GuestMessagingProcessorService비회원 SMS/RCS 메시지 처리, Guest Agent 실행 (미구현)
AgentResponseHandlerAgent 응답 → Sendbird 메시지 변환/전송 (청킹 포함)
AgentResponseRouterAgent 응답 유형별 라우팅 (일반 응답 vs Deferred Tool)
DeferredToolProcessorDeferred Tool 요청 → JSON-RPC 변환/전송
AgentNotificationServiceAgent 기반 알림 생성/발송 (notification_safe_toolsets 사용)
MessageHistoryServiceRedis 기반 대화 이력 관리 (TTL: 30일, 최대 20개)
PendingCallServiceRedis 기반 Pending tool call 추적 (TTL: 1시간)
RAGServiceNumPy Vector Store + OpenAI Embeddings 기반 문서 검색
BotResolutionService사용자 secretary 설정 기반 봇/페르소나 결정
UserProfileService사용자 프로필 조회 (fallback 포함)
ConversationTaskRegistry대화별 Background task 관리. 새 메시지 수신 시 기존 task 자동 취소 (cancel-on-new-message)
LoopInvitationNotifier룹 초대/참여 알림 발송 (각 참여자의 봇/페르소나로 개인화)
GuestInvitationNotifier비회원 SMS 초대 발송
LoopCardBuilderLoop Card (RichContent) 생성. 채팅 응답, 초대, 확정, 업데이트, SQS 트리거 등 모든 알림 경로에서 사용

Application Factory 패턴

python
# main.py
def create_app() -> FastAPI:
    """FastAPI 인스턴스 생성 및 설정"""
    app = FastAPI(...)
    # Lifespan, routes, middleware 설정
    return app

app = create_app()  # FastAPI CLI용 전역 인스턴스

장점:

  • 테스트용 독립 인스턴스 생성 가능
  • fastapi dev 명령어로 직접 실행
  • Auto-reload 지원

관측성 (Observability)

APM/Tracing은 Logfire, 에러 추적은 Sentry, 구조화 로깅은 structlog가 담당합니다.

Logfire 통합

계측 대상자동 추적 내용
pydantic-aiAgent 실행, Tool 호출, LLM 요청
pydanticPydantic 모델 검증
httpx외부 API 호출 모니터링
RedisRedis 명령어 추적
FastAPIHTTP 요청/응답 (/health 제외)

Sentry 통합

에러 추적 전용 (traces_sample_rate=0). Logfire와 역할 분리:

통합역할
FastAPIHTTP 요청 컨텍스트
asyncioBackground task 에러 캡처
logginglogger.exception() 호출 시 Sentry 이벤트 생성
httpxHTTP 클라이언트 에러

구조화 로깅 (structlog)

JSON 형식으로 stdout에 출력하여 CloudWatch가 자동 수집합니다. logging.getLogger() 호출이 JSON 포맷으로 변환됩니다.

초기화 순서: init_sentry()init_logfire()init_logfire_fastapi(app)


변경 이력

날짜변경 내용
2026-03-11LoopCardBuilder: 모든 알림 경로(초대, 확정, 업데이트, SQS 트리거)에서 Loop Card RichContent 사용하도록 반영
2026-03-11비회원 Agent / SMS two-way 기능을 미구현 상태로 정정
2026-03-10비회원 Agent, ConversationTaskRegistry, EventProcessorService, structlog/Sentry 관측성 업데이트