Skip to content

Tool 시스템

truloop-assistant의 pydantic-ai 도구 시스템, Deferred Tool 패턴, JSON-RPC 2.0 프로토콜을 설명합니다.


도구 시스템 개요


등록된 도구 목록

회원 Agent 도구 (all_toolsets, 9개)

도구 파일실행 위치기능주요 함수
datetime_tools.py서버요일 조회, 현재 시각 (타임존 변환)get_day_of_week, get_current_datetime
user_tools.py서버사용자 프로필, 사용자 검색 (연락처/username/룹), EID 일괄 조회get_user_profile, search_users, get_user_eids
knowledge_tools.py서버RAG 기반 지식 검색 (Notion 문서)search_knowledge_base
memory_tools.py서버Supermemory 장기 기억 저장/검색/삭제search_memories, save_memory, forget_memory
places_tools.py서버Google Maps 장소 검색 (비동기 thread pool)search_places
relay_tools.py서버사용자 간 메시지 릴레이 (Agent 기반 알림 생성)send_message_to_user
event_tools.py하이브리드일정 CRUD (서버) + 캘린더 조회 (Deferred). create_event, update_event에서 availability_deadline 파라미터 지원get_events, create_event, invite_to_event
interaction_tools.py클라이언트확인 다이얼로그 (OS 권한 요청 포함 가능)confirm_interaction
permission_tools.py클라이언트OS 권한 상태 확인 (요청 없이 조회만)get_permission_status

비회원 Agent 도구 (guest_toolset, 1개)

주의

미구현: 비회원 Agent 도구는 코드 scaffolding으로 존재하지만, 비회원 Agent 자체가 미구현이므로 프로덕션에서 사용되지 않습니다.

도구 파일실행 위치기능주요 함수
guest_tools.py서버가용 시간 수집, 일정 조회, 초대 목록update_guest_availability, get_event_details, list_invited_events

알림 안전 도구 (notification_safe_toolsets)

Agent 기반 알림 생성 시 사용하는 안전한 도구 세트. 이벤트 생성/초대 도구를 제외하여 재귀 API 호출 방지:

포함제외 (사유)
datetime_toolsetevent_toolset (재귀 이벤트 생성 방지)
knowledge_toolsetuser_toolset (의도치 않은 조회 방지)
interaction_toolsetrelay_toolset (의도치 않은 메시지 발송 방지)
permission_toolset
places_toolset
memory_toolset

공통

파일역할
validation.py도구 입력 검증 유틸리티 (가용 날짜 형식 검증 등)

즉시 실행 도구

서버에서 바로 실행되는 도구입니다. FunctionToolset 인스턴스에 @toolset.tool 데코레이터로 등록합니다.

python
from pydantic import Field
from pydantic_ai import FunctionToolset, RunContext

from truloop_assistant.agent.deps import AgentDeps

datetime_toolset = FunctionToolset[AgentDeps]()

@datetime_toolset.tool
async def get_day_of_week(
    ctx: RunContext[AgentDeps],
    date: str | None = Field(default=None, description="ISO format (YYYY-MM-DD)"),
) -> DayOfWeekResult:
    """특정 날짜의 요일을 반환합니다."""
    ...

도구 설계 원칙:

  • FunctionToolset[AgentDeps] 인스턴스를 모듈 레벨에서 생성하고 @toolset.tool로 등록
  • 단일 책임 (하나의 도구 = 하나의 기능)
  • 서술적 docstring (Agent가 도구 설명을 읽어 호출 결정)
  • Field(description=...) 으로 파라미터 설명 제공
  • 성공 시 Pydantic BaseModel 반환, 실패 시 str 에러 메시지 반환 (Union 패턴)
  • API 오류 시 ModelRetry 예외로 Agent에게 재시도 기회 제공

Deferred Tool 시스템

개념

Deferred Tool은 서버가 아닌 클라이언트 디바이스에서 실행되는 도구입니다. OS 권한이 필요한 작업(캘린더, 위치 등)을 처리하기 위한 Human-in-the-Loop 패턴입니다.

구현 원리

Deferred Tool은 CallDeferred 예외를 발생시켜 Agent 실행을 일시 중단합니다:

python
from pydantic import Field
from pydantic_ai import FunctionToolset, RunContext
from pydantic_ai.exceptions import CallDeferred

from truloop_assistant.agent.deps import AgentDeps

permission_toolset = FunctionToolset[AgentDeps]()

@permission_toolset.tool
async def get_permission_status(
    ctx: RunContext[AgentDeps],
    notification_message: str = Field(
        description="권한 상태 확인 중 표시할 메시지",
    ),
    permission: str = Field(
        description="확인할 OS 권한 (예: 'calendar.read')",
    ),
) -> str:
    """OS 권한 상태를 확인합니다 (요청 없이 조회만)."""
    raise CallDeferred()  # Agent 실행 중단, 클라이언트에 위임

Agent는 DeferredToolRequests를 반환하고, 서버는 이를 JSON-RPC 요청으로 변환하여 Sendbird를 통해 클라이언트에 전달합니다.

정보

notification_message 파라미터는 클라이언트가 Deferred Tool 실행 중 사용자에게 표시하는 대기 메시지입니다. JSON-RPC params에는 포함되지 않고 별도로 처리됩니다.


JSON-RPC 2.0 프로토콜

클라이언트-서버 간 Deferred Tool 통신은 JSON-RPC 2.0 표준을 따릅니다.

요청 (서버 → 클라이언트)

Sendbird 메시지로 전달됩니다 (custom_type: "agent-rpc"):

json
{
    "jsonrpc": "2.0",
    "id": "call_abc123",
    "method": "calendar.getEvents",
    "params": {
        "startDate": "2026-03-09T00:00:00+09:00",
        "endDate": "2026-03-15T23:59:59+09:00"
    }
}

성공 응답 (클라이언트 → 서버)

json
{
    "jsonrpc": "2.0",
    "id": "call_abc123",
    "result": {
        "events": [
            {
                "id": "event_1",
                "title": "팀 미팅",
                "start": { "dateTime": "2026-03-10T10:00:00+09:00" },
                "end": { "dateTime": "2026-03-10T11:00:00+09:00" }
            }
        ]
    }
}

에러 응답 (클라이언트 → 서버)

json
{
    "jsonrpc": "2.0",
    "id": "call_abc123",
    "error": {
        "code": -32010,
        "message": "PermissionRequired",
        "data": {
            "requiredPermission": "calendar.read",
            "canPromptAgain": true
        }
    }
}

에러 코드

코드이름설명
-32010PermissionRequired캘린더 권한 미부여
-32001PermissionDenied사용자가 권한 거부
-32002UserCanceled사용자가 작업 취소

Deferred Tool 전체 흐름

사용자 메시지 수신

사용자가 Sendbird로 "이번 주 일정 알려줘" 전송 → Webhook으로 서버 수신

Agent 처리 및 Tool 호출 결정

Agent가 메시지를 분석하고 get_calendar_events 도구 호출을 결정

CallDeferred 발생

도구가 CallDeferred 예외를 발생 → Agent가 DeferredToolRequests 반환

Pending Call 저장 및 JSON-RPC 전송

서버가 Redis에 Pending call 저장 (TTL: 1시간) → Sendbird로 JSON-RPC 요청 전송 (custom_type: "agent-rpc")

클라이언트 처리

클라이언트가 Sendbird 메시지 수신 → OS 캘린더 권한 요청 → 캘린더 이벤트 조회

Tool 결과 제출

클라이언트가 POST /api/v1/agent/tool-message로 JSON-RPC 응답 전송

Agent 재개 및 최종 응답

서버가 Redis에서 Pending call 조회 → DeferredToolResults로 Agent 재개 → 최종 응답을 Sendbird로 전송 → Pending call 삭제


PendingCallService

Redis에 Deferred Tool 호출을 추적합니다:

항목설명
저장소Redis
키 형식Tool call ID 기반
TTL1시간
저장 데이터Tool call ID, 메서드, 파라미터, 사용자 정보, 채널 정보

클라이언트 구현 가이드

1. Sendbird 메시지 감지

typescript
// custom_type이 "agent-rpc"인 메시지 감지
if (message.customType === 'agent-rpc') {
    const rpcRequest = JSON.parse(message.data)
    handleRPCRequest(rpcRequest)
}

2. OS 권한 요청 및 실행

typescript
const permission = await requestCalendarPermission()
if (!permission.granted) {
    // 에러 응답 전송
    return submitToolError(rpcRequest.id, { code: -32010, ... })
}
const events = await queryCalendar(params)

3. 결과 제출

typescript
await fetch('/assistant/api/v1/agent/tool-message', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${jwtToken}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        jsonrpc: '2.0',
        id: rpcRequest.id,
        result: { events }
    })
})

Deferred Tool Handler 패턴

각 Deferred Tool은 Handler 클래스를 가지며, DeferredToolHandler 프로토콜을 구현합니다:

python
class InteractionToolHandler:
    def get_tool_name(self) -> str:       # "confirm_interaction"
    def get_method_name(self) -> str:     # "interaction.confirm"
    def create_params(self, call) -> ...: # ToolCallPart → JSON-RPC params
    def validate_result(self, data) -> ...: # Raw dict → 타입 모델
    def transform_result(self, v) -> ...: # 결과 변환 (필요 시)
HandlerTool 이름JSON-RPC 메서드
EventToolHandlerget_calendar_eventscalendar.getEvents
InteractionToolHandlerconfirm_interactioninteraction.confirm
PermissionToolHandlerget_permission_statuspermissions.getStatus

변경 이력

날짜변경 내용
2026-03-12event_tools에 availability_deadline 파라미터 지원 추가 (create_event, update_event)
2026-03-11비회원 Agent / SMS two-way 기능을 미구현 상태로 정정
2026-03-10도구 목록 전면 업데이트 (회원/비회원/알림 안전 분류), FunctionToolset 패턴, Deferred Tool Handler 추가