Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
AIっぽい文章を採点して人間らしく直すアプリを作ってみた
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Yuuki Yamashita
June 16, 2026
Technology
110
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
AIっぽい文章を採点して人間らしく直すアプリを作ってみた
Yuuki Yamashita
June 16, 2026
More Decks by Yuuki Yamashita
See All by Yuuki Yamashita
デブサミ福岡CFP提出用
yama3133
1
16
自称宇宙最速で不合格となったAIP-C01にリベンジを果たすべくAIで問題集アプリを作ってみた。
yama3133
0
280
なぜ、私がCommunity Builderに?〜活動期間1か月半でも選出されたワケ〜
yama3133
1
160
ぼくがかんがえたさいきょうのあうとぷっと
yama3133
0
230
Amazon S3 Filesについて
yama3133
2
250
AWSで2番目にリリースされたサービスについてお話しします(諸説あります)
yama3133
1
180
ほんとだもん!アマコネでもMCP使えるもん!ウソじゃないもん!
yama3133
1
120
Amazon Qはアマコネで頑張っています〜 Amazon Q in Connectについて〜
yama3133
1
210
僕、S3 シンプルって名前だけど全然シンプルじゃありません よろしくお願いします
yama3133
1
280
Other Decks in Technology
See All in Technology
AIソロプレナー時代に2ヶ月で20人増員した事業創造会社の開発組織の話
miyatakoji
0
360
MCP Appsを作ってみよう
iwamot
PRO
4
320
Kubernetesにおける学習基盤とLLMOpsの概要
ry
1
170
Rancherの紹介&Update情報(RancherJP Online Meetup #09)
yoshiyuki_kono
0
140
Taking back control of your AI development
inesmontani
PRO
0
110
データ基盤をDataformで整えた話 〜 開発環境を添えて 〜
takapy
0
130
製造業のクラウド活用最適解〜AI,DXを加速するデータ基盤の作り方〜
hamadakoji
0
430
「エンジニア進化論」2028年の開発完全自動化、エンジニアはどう進化するか
cyberagentdevelopers
PRO
2
460
個人最適 から 全体最適 へ AI情報共有会・AIギルド・AI-DLC で進める カンリーの組織展開
rfdnxbro
0
2.1k
Databricks における 生成AIガバナンスの実践
taka_aki
1
360
社内 AI エージェント Synapse と セマンティックレイヤーの育て方
hiroakis
1
1.2k
Reliability in the Age of AI: Engineering for AI Velocity
rrreeeyyy
0
120
Featured
See All Featured
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
220
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.3k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
410
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.2k
Exploring anti-patterns in Rails
aemeredith
3
400
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.3k
Making the Leap to Tech Lead
cromwellryan
135
9.9k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
140
A designer walks into a library…
pauljervisheath
211
24k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
120k
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
220
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
140
Transcript
> JAWS-UG AI/ML 支部 // 2025.06.16 AI っぽい文章を採点して 人間らしく直すアプリを作った Amazon
Bedrock × Strands Agents × AgentCore Gateway TypeScript SDK × AgentCore Gateway — 実装してわかったこと AI 文章判定くん 山下 祐樹 JAWS-UG AI/ML #40:AIエージェントとAI-DLCで加速する!AI/ML実践LT大会
自己紹介 山下 祐樹 都内Sier企業にてCCoE AWS Community Builder (AI Engineering)
AI 文章判定くん — できること 1 AI っぽさスコア(0 〜 100) 文章の
AI っぽさを数値化。断定ではなく目安として提 示。 UI にも「誤りを含む・判定の根拠に使わないで」 と明記。 2 具体的な指摘 機械的な接続詞・無難な一般論・定型的な締め・均一 なリズム・具体性の欠如を根拠つきで指摘。 3 人間らしくリライト Strands Agents の反復ループでスコアが基準を下回るま で自動リライトを繰り返す。 日本語 / 英語 対応 テキスト貼り付け .txt / .md / .pdf 入力に対応 MCP ツールとして公開 Claude Code などから 直接呼び出し可能 92 → 12 の実績 Strands Agents の反復ループで AI っぽさスコアを大幅削減 > score_demo.ts // リライト前 92 // リライト後 12 反復ループで 92 → 12 まで 削減 > READY_
DEMO アプリ画面 試してみてください! ai-text-checker-pi.vercel.app GitHub github.com/yama3133/ai-text-checker > SCAN TO TRY_
設計判断:断定しない、寄せる NG 断定アプローチ 「これは AI 文章です」 二択で断定する • 誤検知が多い •
日本語は特に判定が難しい • 人間が手を入れた AI 文章は判別不能 • 「断定」は現状技術では不可能 • UI に「判定の根拠に使わないで」と書く羽目になる OK LLM に寄せる 「AI っぽさ」を添削する スコア + 指摘 + リライト • AI っぽさスコア(0 〜 100)を目安として提示 • 機械的な接続詞・無難な一般論・定型的な締 めを指摘 • 人間らしいリライト案を生成 • LLM が得意なことだけやる • UI にも「断定ではない・誤りを含む」と明記
Next.js 16 + Vercel App Router / Tailwind / フロントエンド
Amazon Bedrock Converse API / Claude Sonnet 4.6 / us-east-1 Strands Agents SDK TypeScript — 採点 → リライト → 再採点ル ープ AgentCore Gateway MCP エンドポイント / JWT 認証 (Cognito) AWS Lambda Node 22 / MCP ツール実装 / AWS SDK v3 同 梱
モデル選定: list に出ることと invoke できることは別 > 最初の試み Claude Opus 4.8
を使おうと した 403 AccessDenied • list-foundation-models に出てく る • 実際に invoke できるとは限ら ない • リージョン・アカウント設定 によってアクセス不可 • リストを信じて実装を進めると後で詰 まる > 解決策 Converse で ping を 1 行打って確認 確認コマンド Bash aws bedrock-runtime converse ¥ --region us-east-1 ¥ --model-id us.anthropic.claude-sonnet-4-6 ¥ --messages '["role" : "user" , "content" :["text" : "ping"}]}] ' ¥ --inference-config '"maxTokens" :5}' → Sonnet 4.6 に決定 • Converse API は invoke の実際の可否を返す • モデル ID に乗る前に必ず確認する習慣を • maxTokens: 5 で十分 — 応答速度も速い 教訓:モデル ID に乗る前に、 Converse で実際のアクセス可否を確認する
Strands Agents とは LLM エージェントを数行で書けるフレームワーク ツールを渡して run() を呼ぶだけ。 LLM が目標達成まで自律的に
ループ。 主要クラス Agent エージェント本体。 tools と model を受け取り run() で実行 tool() ツール定義。 Zod スキーマで型付 き引数を宣言 BedrockModel Bedrock バックエンド。 modelId を渡すだけ structuredOutputSchema 出力を Zod スキーマで型付き JSON に強制 TypeScript SDK · @strands-agents/sdk エージェントループ detect_ai_style 採点(スコア算出) スコア ≤ 20 ? YES NO rewrite_text リライト 繰 り 返 し 完了 — 結果を返す TypeScript SDK · @strands-agents/sdk ツールを渡して run() を呼ぶだけ — LLM が自律的にループする
反復リライトループ — コードで見ると 採点 → リライト → 再採点 → …
を自動化 ループ処理は自分で書かない。 LLM が目標スコアを達成するまで自律的に繰り返す 。 ハマりポイント① バンドル問題 Next.js では SDK がバンドルされてしまいエラーに。 serverExternalPackages に追加して外部化が必要。 TypeScript // next.config.ts const nextConfig: NextConfig = { serverExternalPackages: [ "@strands-agents/sdk", "pdf-parse-fork", ], }; export default nextConfig; ハマりポイント② Vercel Hobby タイムアウト ループは 20 〜 45 秒かかる。 Hobby プランの上限 60 秒を超え得る。 import Agent, tool, BedrockModel } from "@strands-agents/sdk"; import z } from "zod"; // AI っぽさスコアを計算するツール const detectAiStyle = tool({ name: "detect_ai_style", description: "文章の AI っぽさをスコアリング", inputSchema: z.object( text: z.string() }), handler: async ( text }) => { return score: await scoreText(text), issues: [...] }; }, }); // リライトするツール const rewriteText = tool({ name: "rewrite_text", description: "AI っぽさを下げるようにリライト", inputSchema: z.object( text: z.string(), issues: z.array(z.string()) }), handler: async ( text, issues }) => { return rewritten: await rewrite(text, issues) }; }, }); // エージェントループ — スコアが閾値を下回るまで繰り返す const agent = new Agent({ model: new BedrockModel( modelId: "us.anthropic.claude-sonnet-4-6" }), tools: [detectAiStyle, rewriteText], systemPrompt: "スコアが 20 以下になるまでリライトを繰り返してください。", }); const result = await agent.run(inputText); 教訓: Strands を使えばツールを使う反復リライトが数行の型付きコードになる
実際に動かしてみた結果 BEFORE 92 AI っぽさスコア 3 回ループ AFTER 12 AI
っぽさスコア ツールを使う反復リライトが、数行の型付きコードになった。 実際に動かして出た数字。 Strands Agents 、ちゃんと動きます。 92 → 12 ちゃんと動いた
ハマり ① Next.js でバンドルが壊れる 問題 SDK がバンドルで壊れる Strands Agents SDK
は Node.js ネイティブモジュールを使 用。 Next.js の Webpack がバンドルしようとすると壊れる。 → 謎のビルドエラー・実行時エラーで詰まる 解決 策 serverExternalPackages を追加 next.config.ts に serverExternalPackages を追加するだけ。 SDK を Webpack のバンドル対象から外す。 → これだけで動く。シンプル。 pdf-parse-fork も同様に外部化が必要 → BUNDLED_EXTERNALLY — OK TypeScript // next.config.ts const nextConfig: NextConfig = { serverExternalPackages: [ "@strands-agents/sdk", "pdf-parse-fork", ], }; export default nextConfig; この 2 行を追加するだけ • @strands-agents/sdk — Strands Agents 本体 • pdf-parse-fork — PDF 読み込みライブラリ なぜ必要か Strands SDK は Node.js ネイティブバインディングを含む。 Next.js の Webpack はこれをブラウザ向けにバンドルしようと して失敗する。 serverExternalPackages で「このパッケージはバンドルしない 」と明示する。
ハマり ② Vercel Hobby のタイムアウト リライトループの実行時間 20 〜 45 秒
採点 → リライト → 再採点 … のループ vs Vercel Hobby タイムアウト上限 60 秒 Serverless Function の最大実行時間 最短 20 秒 最長 45 秒 上限 60 秒 ! 対策 Vercel Pro にアップグレード タイムアウト上限が 300 秒 に延長 。 根本的な解決策。 バックグラウンド処理に切り 替え 非同期ジョブキューで処理。 ポーリングで結果を取得する設計 に。 ループ回数を制限(暫定) 最大反復回数を設定して 確実に 60 秒以内に収める。
AgentCore Gateway とは? Amazon Bedrock AgentCore What is it? Lambda
を MCP ツールにする マネージドゲートウェイ 自分で書いた Lambda 関数を MCP プロトコル経由で AI エージェントから呼び出せる。 Claude Code などの MCP クライアントが直接ツール として認識できる。 MCP = Model Context Protocol — AI エージェントがツールを呼 ぶ標準プロトコル できること Lambda を MCP ツールとして外部公開 Claude Code などから直接呼び出し可能 サーバーレスで MCP サーバーを立てられる このアプリも MCP ツールとして公開済み ✓ Flow 呼び出しの流れ MCP クライアント(Claude Code など) MCP / JWT AgentCore Gateway invoke AWS Lambda(ツール実装) Converse API Amazon Bedrock(Claude) Lambda は Node 22 の 1 ファイル。 AWS SDK v3 同梱なので追加 依存なし。
最大の驚き AgentCore Gateway SURPRISE 無認証の Gateway は作れない authorizerType は CUSTOM_JWT
の一択。無認証エンドポイントは存在しない。 対応策 1 Cognito User Pool 作成 User Pool ID を控えておく 2 M2M App Client 追加 client_credentials フロー 3 discoveryURL を Gateway 指定 Cognito の OIDC discovery URL クライアント側はこれだけ // JWT を取得(Cognito client_credentials) const token = await getCognitoToken(); // MCP JSON-RPC に Bearer トークンを付けるだけ Authorization: Bearer <token> トークンは短命 — クライアントシークレットはリポに置かず、 AWS 認証経由で実行時に都度 JWT 取得
AgentCore Gateway :認証は必須 authorizerType: CUSTOM_JWT のみ Cognito M2M フ ロー
無認証エンドポイントは作れ ない Cognito User Pool + M2M App Client が必須 ① User Pool 作成 ② App Client (client_credentials) ③ Gateway に discoveryUrl 指定 TIP: 「無認証は無理」を前提に設計する 。最初から Cognito を組み込む。 Bash # ① Cognito User Pool 作成 aws cognito-idp create-user-pool ¥¥ --pool-name "agentcore-pool" # ② App Client (client_credentials) aws cognito-idp create-user-pool-client ¥¥ --user-pool-id <pool-id> ¥¥ --client-name "gateway-client" ¥¥ --allowed-oauth-flows "client_credentials" ¥¥ --generate-secret # ③ Gateway 作成 aws bedrock-agentcore create-gateway ¥¥ --name "ai-text-checker-gateway" ¥¥ --role-arn <execution-role-arn> ¥¥ --authorizer-type CUSTOM_JWT ¥¥ --authorizer-configuration ¥¥ '{"customJWTAuthorizer" :{"discoveryUrl" : "https://cognito- idp.<region>.amazonaws.com/<pool-id>/.well-known/openid-configuration" }}' 設計のポイント • 「無認証は無理」を前提に設計する • クライアントシークレットはリポに置かず、実行時に都度 JWT 取得 • authorizerType: CUSTOM_JWT のみ対応 • discoveryUrl は Cognito の OpenID Connect エンドポイントを指定
i18n 日英対応:翻訳しない、その言語で答える 「翻訳しない」指示 入力言語を検出し、同じ言語で回答するよう指示。 「英語に翻訳してから答えて」はニュアンスがズレる 言語検出は LLM に任せる "Detect the
language of the input and respond in the same language." たった 1 行のシステムプロンプトで日英両対応が完成 結果 追加の翻訳ロジック不要。コード 0 行で多言語対応。 LLM が得意なことは LLM に任せる
MCP クライアントからの呼び出し方 Step 1: JWT 取得 Cognito から access_token を取得
トークンは短命 → 毎回リクエスト前に取得する。 シークレットはリポに置かず AWS 認証経由で取得。 TypeScript // 1. Cognito から JWT トークン取得 const tokenRes = await fetch( `https://$USER_POOL_DOMAIN}/oauth2/token`, { method: "POST", headers: "Content-Type": "application/x-www-form- urlencoded" }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: CLIENT_ID, client_secret: CLIENT_SECRET, scope: "ai-text-checker/invoke", }), } ); const access_token } = await tokenRes.json(); Step 2: tools/call Bearer JWT 付きで MCP JSON-RPC Authorization: Bearer <token> を付けるだけ。 ツール名は detect___detect_ai_style 形式で指定。 TypeScript // 2. MCP JSON-RPC 呼び出し(Bearer JWT 付き) const mcpRes = await fetch(GATEWAY_URL, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer $access_token}`, }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "detect___detect_ai_style", arguments: text: inputText }, }, }), }); const result = await mcpRes.json(); initialize → tools/list → tools/call ← MCP ハンドシェイク順序(直接 tools/call でも動作)
まとめ — 4 つの教訓 作ってみてわかった。でも、ちゃんと動いた。 1 「これは AI ?」には正直に 断定はしない。目安スコア+具体的な指摘+リライトに寄せる
。 LLM が得意なことに設計を合わせる。 TypeScript // 断定しない設計 const result = { aiScore: 73, // 0-100 の目安 feedback: [...], // 具体的な指摘 rewritten: "..." // リライト案 }; 2 モデル ID より先に Converse で確認 list-foundation-models に出ることと invoke できることは別。 1 行の Converse 呼び出しでアクセス可否を確認してから進む。 Bash aws bedrock-runtime converse ¥ --region us-east-1 ¥ --model-id us.anthropic.claude-sonnet-4-6 ¥ --messages '["role" : "user" , "content" :["text" : "ping"}]}] ' ¥ --inference-config '"maxTokens" :5}' 3 Strands は外部化してから使う 反復リライトループが数行の型付きコードになる。 ただし Next.js では必ず serverExternalPackages で外部化する。 TypeScript // next.config.ts serverExternalPackages: [ "@strands-agents/sdk", "pdf-parse-fork" ] 4 Gateway は JWT 必須で設計する Lambda を MCP ツールにする手段として綺麗。 ただし無認証エンドポイントは作れない。最初から Cognito を 前提に。 Text authorizerType: CUSTOM_JWT のみ → Cognito User Pool + M2M app client → discoveryURL を指定して完了 → クライアントシークレットはリポに置かない
ご清聴いただき ありがとうございました