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
Yuuki Yamashita
June 16, 2026
Technology
27
1
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
15
自称宇宙最速で不合格となったAIP-C01にリベンジを果たすべくAIで問題集アプリを作ってみた。
yama3133
0
270
なぜ、私が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
やさしいA2A入門
minorun365
PRO
5
320
会社紹介資料 / Sansan Company Profile
sansan33
PRO
18
420k
AI Engineering Summit Tokyo 2026 AIの前に、やることがある 〜医療データ企業の4フェーズ〜
dtaniwaki
0
2.2k
コードレビューを制するチームがソフトウェアデリバリーのフローを制す / Beyond Code Review: Distributing Its Responsibilities Across the SDLC
mtx2s
4
1.3k
protovalidate-es を導入してみた
bengo4com
0
150
TypeScript Compiler APIとPHP-Parserを活用し、TypeScriptとPHPで型を共有する
shuta13
0
370
MIERUNE JCT 発表資料「宇宙から伊能忠敬ごっこ」
syuchimu
0
190
AI-DLCを活用した高品質・安全なAI駆動開発実践 / AI Driven Development with AI-DLC
yoshidashingo
0
150
個人の発見を、組織の知恵に 〜生成AI活用を"探索"から"組織の仕組み"へ〜
kintotechdev
3
1.1k
AIの性能が向上しても未解決な組織の重大問題は何か?/An Unsolved Organizational Problem in the Age of AI
moriyuya
2
280
製造業のクラウド活用最適解〜AI,DXを加速するデータ基盤の作り方〜
hamadakoji
0
410
Microsoft Build Keynoteふりかえり
tomokusaba
0
110
Featured
See All Featured
Skip the Path - Find Your Career Trail
mkilby
1
140
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
140
30 Presentation Tips
portentint
PRO
1
320
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
840
From π to Pie charts
rasagy
0
200
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.8k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.3k
Discover your Explorer Soul
emna__ayadi
2
1.1k
Facilitating Awesome Meetings
lara
57
6.9k
Why You Should Never Use an ORM
jnunemaker
PRO
61
9.9k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.5k
AI: The stuff that nobody shows you
jnunemaker
PRO
8
690
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 を指定して完了 → クライアントシークレットはリポに置かない
ご清聴いただき ありがとうございました