다크 모드
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_type | AgentDeps | GuestAgentDeps |
| output_type | AgentOutput | DeferredToolRequests | AgentOutput |
| toolsets | 9개 (all_toolsets) | 1개 (guest_toolset) |
| 기능 | 일정 관리, 장소 검색, 메시지 릴레이, RAG, 장기 기억 등 | 가용 시간 수집, 일정 조회 |
| Deferred Tool | 지원 (캘린더, 권한, 확인 다이얼로그) | 미지원 |
회원 Agent 구성 요소
| 구성 요소 | 설명 |
|---|---|
| Model | OpenRouter (Claude Sonnet 4.6) |
| deps_type | AgentDeps - 런타임 의존성 |
| output_type | AgentOutput | DeferredToolRequests |
| instructions | 정적 기본 프롬프트 (XML 태그 기반) |
| toolsets | 9개 도구 세트 (all_toolsets) |
| dynamic instructions | Per-request 동적 프롬프트 (사용자 컨텍스트, 페르소나) |
Instruction 계층
| 유형 | 시점 | 내용 |
|---|---|---|
| Static | Agent 초기화 시 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줄 |
| Service | Agent 실행, 외부 서비스 연동, 에러 처리 | 비즈니스 로직 전체 |
주요 서비스
| 서비스 | 역할 |
|---|---|
WebhookProcessorService | Sendbird Webhook 메시지 처리, Agent 실행, 응답 라우팅. Pending deferred tool 자동 취소/복구 포함 |
ToolMessageProcessorService | Tool 결과 검증, Agent 재개, 응답 전송 |
EventProcessorService | truloop-core Internal 이벤트 처리 (봇 선택, 일정 초대, 비회원 참여 등). Discriminated union 패턴 매칭 |
GuestMessagingProcessorService | 비회원 SMS/RCS 메시지 처리, Guest Agent 실행 (미구현) |
AgentResponseHandler | Agent 응답 → Sendbird 메시지 변환/전송 (청킹 포함) |
AgentResponseRouter | Agent 응답 유형별 라우팅 (일반 응답 vs Deferred Tool) |
DeferredToolProcessor | Deferred Tool 요청 → JSON-RPC 변환/전송 |
AgentNotificationService | Agent 기반 알림 생성/발송 (notification_safe_toolsets 사용) |
MessageHistoryService | Redis 기반 대화 이력 관리 (TTL: 30일, 최대 20개) |
PendingCallService | Redis 기반 Pending tool call 추적 (TTL: 1시간) |
RAGService | NumPy Vector Store + OpenAI Embeddings 기반 문서 검색 |
BotResolutionService | 사용자 secretary 설정 기반 봇/페르소나 결정 |
UserProfileService | 사용자 프로필 조회 (fallback 포함) |
ConversationTaskRegistry | 대화별 Background task 관리. 새 메시지 수신 시 기존 task 자동 취소 (cancel-on-new-message) |
LoopInvitationNotifier | 룹 초대/참여 알림 발송 (각 참여자의 봇/페르소나로 개인화) |
GuestInvitationNotifier | 비회원 SMS 초대 발송 |
LoopCardBuilder | Loop 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-ai | Agent 실행, Tool 호출, LLM 요청 |
| pydantic | Pydantic 모델 검증 |
| httpx | 외부 API 호출 모니터링 |
| Redis | Redis 명령어 추적 |
| FastAPI | HTTP 요청/응답 (/health 제외) |
Sentry 통합
에러 추적 전용 (traces_sample_rate=0). Logfire와 역할 분리:
| 통합 | 역할 |
|---|---|
| FastAPI | HTTP 요청 컨텍스트 |
| asyncio | Background task 에러 캡처 |
| logging | logger.exception() 호출 시 Sentry 이벤트 생성 |
| httpx | HTTP 클라이언트 에러 |
구조화 로깅 (structlog)
JSON 형식으로 stdout에 출력하여 CloudWatch가 자동 수집합니다. logging.getLogger() 호출이 JSON 포맷으로 변환됩니다.
초기화 순서: init_sentry() → init_logfire() → init_logfire_fastapi(app)
변경 이력
| 날짜 | 변경 내용 |
|---|---|
| 2026-03-11 | LoopCardBuilder: 모든 알림 경로(초대, 확정, 업데이트, SQS 트리거)에서 Loop Card RichContent 사용하도록 반영 |
| 2026-03-11 | 비회원 Agent / SMS two-way 기능을 미구현 상태로 정정 |
| 2026-03-10 | 비회원 Agent, ConversationTaskRegistry, EventProcessorService, structlog/Sentry 관측성 업데이트 |