구축·운영
도구 승인
AI가 도구를 호출할 때 사용자에게 승인을 요청하고, 승인/거부 상태에 따라 다른 UI를 표시하는 패턴을 설명합니다.
reopt design업데이트
1. 세션 설정
서버 도구가 승인을 요구하면 AI SDK는 tool part를 approval-requested 상태로 전달합니다. approveToolCall / denyToolCall은 이 part의 approval.id로 응답합니다.
tsx
"use client";
import { DefaultChatTransport, lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
import {
useChatSession,
Conversation,
ConversationContent,
Message,
MessageContent,
MessageParts,
PromptInput,
PromptInputFooter,
PromptInputSubmit,
PromptInputTextarea,
} from "@reopt-ai/opt-chat";
export default function ChatWithApproval() {
const session = useChatSession({
transport: new DefaultChatTransport({ api: "/api/chat" }),
// 승인 응답 후 다음 tool step을 자동 진행합니다.
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
});
return (
<div className="flex h-dvh flex-col">
<Conversation>
<ConversationContent>
{session.messages.map((msg) => (
<Message key={msg.id} from={msg.role}>
<MessageContent>
<MessageParts parts={msg.parts} />
</MessageContent>
</Message>
))}
</ConversationContent>
</Conversation>
<PromptInput onSubmit={session.handleSubmit} isLoading={session.isLoading} onStop={session.stop}>
<PromptInputTextarea />
<PromptInputFooter>
<div className="flex-1" />
<PromptInputSubmit />
</PromptInputFooter>
</PromptInput>
</div>
);
}2. Confirmation Compound 컴포넌트
Confirmation은 compound 패턴으로 상태별 콘텐츠를 분리합니다. 각 하위 컴포넌트는 현재 승인 상태에 따라 조건부로 렌더링됩니다.
| 컴포넌트 | 역할 |
|---|---|
| Confirmation | 도구 승인 UI의 루트 컨테이너. AI SDK part.state와 approvalId를 관리합니다. |
| ConfirmationContent | 도구 호출의 이름, 인자 등 상세 내용을 보여주는 영역입니다. |
| ConfirmationRequest | 승인 대기 상태에서 표시되는 콘텐츠. '이 도구를 실행할까요?' 메시지 등. |
| ConfirmationAccepted | 승인 후 표시되는 콘텐츠. 실행 중/완료 상태를 보여줍니다. |
| ConfirmationRejected | 거부 후 표시되는 콘텐츠. '사용자가 거부했습니다' 메시지 등. |
| ConfirmationActions | 승인/거부 버튼을 배치하는 영역. 기본 버튼 쌍을 제공합니다. |
tsx
import {
Confirmation,
ConfirmationContent,
ConfirmationRequest,
ConfirmationAccepted,
ConfirmationRejected,
ConfirmationActions,
} from "@reopt-ai/opt-chat";
// renderPart에서 approval-requested tool part를 감지해 Confirmation을 렌더합니다.
<Confirmation
state={part.state}
approvalId={part.approval?.id}
approved={part.approval?.approved}
onApprove={session.approveToolCall}
onDeny={session.denyToolCall}
>
<ConfirmationContent>
<p>도구: {part.type.replace("tool-", "")}</p>
<pre>{JSON.stringify(part.input, null, 2)}</pre>
</ConfirmationContent>
<ConfirmationRequest>
<p>이 도구를 실행할까요?</p>
<ConfirmationActions />
</ConfirmationRequest>
<ConfirmationAccepted>
<p>도구 실행이 승인되었습니다.</p>
</ConfirmationAccepted>
<ConfirmationRejected>
<p>사용자가 도구 실행을 거부했습니다.</p>
</ConfirmationRejected>
</Confirmation>3. 수동 승인/거부
Confirmation 컴포넌트를 사용하지 않고 직접 승인/거부를 제어하려면, session의 approveToolCall과 denyToolCall 메서드를 호출합니다.
tsx
// 수동으로 승인/거부를 제어하는 경우
const session = useChatSession({
transport: new DefaultChatTransport({ api: "/api/chat" }),
});
// AI SDK part 객체 또는 part.approval.id 문자열을 모두 받을 수 있습니다.
session.approveToolCall(part);
session.approveToolCall(part.approval.id);
session.denyToolCall(part, "사용자가 거부했습니다");