
AI 글쓰기 탐지 및 인간 스타일로 재작성하기 — Bedrock + Strands Agents + AgentCore Gateway
요약
Amazon Bedrock과 Strands Agents를 활용하여 AI 생성 텍스트를 탐지하고 인간 스타일로 재작성하는 AI Text Checker 구축 사례를 소개합니다. 에이전트 루프를 통해 AI 유사도 점수가 임계값 이하로 떨어질 때까지 반복적으로 재작성을 수행하는 기술적 구현 방법을 다룹니다.
핵심 포인트
- Amazon Bedrock의 Claude Sonnet 4.6을 활용한 텍스트 분석
- Strands Agents를 이용한 '통과할 때까지 재작성' 에이전트 루프 구현
- AgentCore Gateway와 MCP를 통한 도구 노출 방식 활용
- 다국어 지원을 위한 정교한 시스템 프롬프트 설계 전략
라이브 데모: https://ai-text-checker-pi.vercel.app
코드: https://github.com/yama3133/ai-text-checker
저는 AI Text Checker(일본어 명칭: 「AI文章判定くん」)를 구축했습니다. 텍스트를 붙여넣으면 해당 글이 얼마나
- Next.js 16 (App Router) + Tailwind, Vercel에 배포
- Amazon Bedrock, Converse API (
us-east-1)를 통한 Claude Sonnet 4.6 - "통과할 때까지 재작성" 루프를 위한 Strands Agents (TypeScript SDK)
- MCP를 통해 도구를 노출하기 위한 Amazon Bedrock AgentCore Gateway
실제로 호출 가능한 모델 선택하기
Opus 4.8을 원했지만, 이 계정에서는 AccessDenied (403) 오류가 반환되었습니다. list-foundation-models / list-inference-profiles에 모델이 나타난다고 해서 반드시 호출할 수 있다는 것을 보장하지는 않습니다. 한 줄짜리 Converse "ping"으로 후보들을 확인한 끝에 Sonnet 4.6을 선택했습니다:
aws bedrock-runtime converse --region us-east-1 \
--model-id us.anthropic.claude-sonnet-4-6 \
--messages '[{"role":"user","content":[{"text":"ping"}]}]'
...
핵심 분석은 엄격한 JSON (aiLikenessScore, label, summary, findings[], humanized)을 반환하는 시스템 프롬프트를 사용한 단일 Converse 호출입니다. 이중 언어 지원을 위해 중요했던 세부 사항은 다음과 같습니다: 해설(commentary)은 UI 언어를 따르지만, 재작성된 결과물은 반드시 원문 언어를 유지해야 합니다. 프롬프트에는 실질적으로 _"레이블과 이유는 영어로 작성하되, 재작성된 내용은 입력값과 동일한 언어로 유지하세요 — 절대 번역하지 마세요."_라고 명시되어 있습니다. 이 문구가 없으면 영어 입력값이 때때로 일본어로 재작성되어 돌아오는 경우가 있었습니다.
Strands Agents를 이용한 "통과할 때까지 재작성"
단 한 번의 재작성만으로는 여전히 AI가 쓴 것 같은 느낌이 들 수 있습니다. 그래서 두 번째 모드가 있습니다. 바로 AI 유사도 점수가 임계값 아래로 떨어질 때까지(또는 최대 재작성 횟수에 도달할 때까지) 점수 측정, 재작성, 재점수 측정, 반복을 수행하는 에이전트입니다. 이것은 진정한 에이전트 루프(agent loop)이며, Strands에 완벽하게 부합합니다.
TypeScript SDK (@strands-agents/sdk)는 Agent, tool() 헬퍼 (Zod 스키마), BedrockModel, 그리고 타입이 지정된 결과를 위한 structuredOutputSchema를 제공합니다:
import { Agent, BedrockModel, tool } from "@strands-agents/sdk";
import { z } from "zod";
...
실제로 모델은 한 번의 재작성(rewrite)만으로도 종종 완벽하게 수행합니다. 100점 만점에 92점이었던 샘플이 12점으로 떨어졌고, 구조화된 출력(structured output)은 무엇이 바뀌었는지 정확히 알려주었습니다.
주의 사항 — 번들링 (bundling). Strands는 많은 선택적 피어 의존성(peer deps)을 포함합니다. Next.js를 사용하는 경우, 번들러가 런타임에 이를 건드리지 않도록 외부(external)로 표시하십시오:
// next.config.ts
const nextConfig = {
serverExternalPackages: ["@strands-agents/sdk", "pdf-parse-fork"],
...
주의 사항 — Vercel 타임아웃 (timeouts). 루프(loop) 과정에서 여러 번의 Bedrock 호출이 발생하며 20~45초가 소요될 수 있습니다. Vercel Hobby 플랜은 함수 실행 시간을 60초로 제한하므로, 긴 자동 재작성 과정은 타임아웃이 발생할 수 있습니다. 로컬 환경에서는 괜찮지만, 프로덕션 환경에서는 이를 고려하여 예산을 책정해야 합니다.
AgentCore Gateway를 통한 MCP 노출
저는 이 도구를 Claude Code에서 호출하고 싶었기에, AgentCore Gateway 뒤에 MCP 서버로 배치했습니다. 구조는 다음과 같습니다:
MCP 클라이언트 (Claude Code) → AgentCore Gateway (MCP / JWT)
→ Lambda (detect_ai_style) → Bedrock (Sonnet 4.6)
Lambda는 단일 Node 파일입니다 (Node 22 런타임은 이미 AWS SDK v3를 포함하고 있으므로 패키징할 의존성이 없습니다). Gateway 타겟은 Lambda를 가리키며 도구 스키마(tool schema)를 전달합니다.
놀라운 사실: AgentCore Gateway의 authorizerType은 CUSTOM_JWT이며, 이것이 유일한 옵션입니다. 익명 게이트웨이는 존재하지 않습니다. 따라서 MCP 클라이언트는 반드시 Bearer JWT를 제시해야 합니다. 저는 client_credentials (머신 간 통신, machine-to-machine) 앱 클라이언트를 가진 Cognito 사용자 풀(user pool)을 설정하고, 게이트웨이의 인증기(authorizer)를 Cognito의 디스커버리 URL(discovery URL)로 지정했습니다:
aws bedrock-agentcore-control create-gateway \
--name ai-text-checker-gateway --protocol-type MCP \
--authorizer-type CUSTOM_JWT \
...
두 가지 더 까다로운 점이 있습니다:
- Gateway는 도구 인자(tool arguments)를
event로 하여 Lambda를 호출하며,context를 통해${targetName}___${toolName}형식으로 도구 이름을 전달합니다. 도구가 하나뿐이라면 라우팅(routing) 과정을 완전히 무시해도 됩니다. - 대상
toolSchema는 속성 정의에서enum을 허용하지 않습니다 — 오직type,properties,required,items,description만 사용할 수 있습니다. 따라서 허용되는 값들을description안으로 옮겼습니다.
설정이 완료되면, 클라이언트로부터의 흐름은 Authorization: Bearer <token>을 포함한 일반적인 MCP JSON-RPC (initialize → tools/list → tools/call) 방식입니다. 도구는 detect___detect_ai_style로 나타납니다. 토큰은 수명이 짧으므로, 클라이언트 비밀키(client secret)를 레포지토리(repo) 외부에 두고 AWS CLI를 통해 런타임에 새로운 JWT를 가져오도록 유지합니다.
핵심 요약 (Takeaways)
- "이것이 AI인가?"라는 질문에 대해서는 솔직해지세요: 판결(verdict)이 아닌 **추정치(estimate)와 편집 피드백(editorial feedback)**을 제공해야 합니다.
- 모델 ID를 기반으로 구축하기 전에, 실제 Converse 호출을 통해 모델 액세스 권한을 확인하세요.
- Strands를 사용하면 반복적인 도구 사용 재작성(iterative, tool-using rewrite) 루프를 몇 줄의 타입이 지정된 코드(typed code)로 구현할 수 있습니다 — 단, Next.js에서는 이를 외부(external)로 표시해야 합니다.
- AgentCore Gateway는 Lambda를 MCP 도구로 변환하는 깔끔한 방법이지만, 익명 엔드포인트가 아닌 필수적인 JWT 인증(Cognito)을 계획해야 합니다.
직접 시도해 보세요: https://ai-text-checker-pi.vercel.app — 그리고 점수가 틀린 부분이 있다면 알려주세요. 때로는 틀릴 것입니다. 그것이 핵심입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기