From 2e819f8ef8efabd25415fd01eea87d52ade12635 Mon Sep 17 00:00:00 2001 From: Yone Date: Tue, 18 Feb 2025 17:40:20 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E3=81=84=E3=82=89=E3=81=AA=E3=81=84?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/lib/gemini.ts | 86 --------------------------------------- 1 file changed, 86 deletions(-) diff --git a/backend/src/lib/gemini.ts b/backend/src/lib/gemini.ts index 6c29fa6..46f02a8 100644 --- a/backend/src/lib/gemini.ts +++ b/backend/src/lib/gemini.ts @@ -139,90 +139,4 @@ const generateTanka = async (originalText: string): Promise => { } }; -/* -const getGeminiText = async (c: Context) => { - try { - // POSTメソッド以外は拒否 - if (c.req.method !== 'POST') { - return c.json( - { - success: 'false', - message: { - status: 405, - message: 'Bad Request', - description: 'POSTメソッドでリクエストしてください。', - }, - }, - 405 - ); - } - - // リクエストボディから prompt を取得 - const { prompt } = await c.req.json(); - if (!prompt) { - return c.json({ error: 'Prompt is required' }, 400); - } - - const apiKey = env.GEMINI_API_KEY; - if (!apiKey) { - throw new Error('APIが設定されていません。'); - } - - const genAI = new GoogleGenerativeAI(apiKey); - const model = genAI.getGenerativeModel({ - model: 'gemini-2.0-pro-exp-02-05', - systemInstruction: `SNSの投稿を短歌にしてください。原文の特徴的な要素はそのままに、比喩表現を使った趣深い短歌にしてください。出力の形式は以下のJSONスキーマに従ってください。各値は日本語で出力してください。 -{ - "type": "object", - "description": "変換した短歌", - "properties": { - "response": { - "type": "array", - "description": "ユーザーアカウント作成のレスポンスデータの配列", - "items": { - "type": "object", - "properties": { - "line1": { - "type": "string", - "description": "短歌の1行目" - }, - "line2": { - "type": "string", - "description": "短歌の2行目" - }, - "line3": { - "type": "string", - "description": "短歌の3行目" - }, - "line4": { - "type": "string", - "description": "短歌の4行目" - }, - "line5": { - "type": "string", - "description": "短歌の5行目" - } - }, - "required": ["line1", "line2", "line3", "line4", "line5"] - } - }, - "required": ["response"] - } -}`, - generationConfig: { responseMimeType: 'application/json' }, - }); - - const result = await model.generateContent(prompt); - const jsonResponse = JSON.parse(result.response.text()); - console.log(jsonResponse); - console.log('-----------------------------'); - - return c.json(jsonResponse); - } catch (error) { - console.error('APIエラー:', error); - return c.json({ error: 'Internal Server Error' }, 500); - } -}; -*/ - export default generateTanka; From b7237f5d2305147fcb4930ae5ca00c8eaa48274f Mon Sep 17 00:00:00 2001 From: Yone Date: Wed, 19 Feb 2025 20:17:25 +0900 Subject: [PATCH 2/3] =?UTF-8?q?JSON=E3=82=B9=E3=82=AD=E3=83=BC=E3=83=9E?= =?UTF-8?q?=E3=81=AE=E6=8C=87=E5=AE=9A=E6=96=B9=E6=B3=95=E3=82=92=E3=80=81?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=81=A0=E3=81=91=E3=83=89=E3=82=AD?= =?UTF-8?q?=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88=E3=81=AE=E6=9B=B8=E3=81=8D?= =?UTF-8?q?=E6=96=B9=E3=81=AB=E8=BF=91=E3=81=A5=E3=81=91=E3=81=9F=200?= =?UTF-8?q?=E5=A7=8B=E3=81=BE=E3=82=8A=E3=81=AE=E8=A6=81=E7=B4=A0=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/lib/gemini.ts | 182 +++++++++++++++++++++++--------------- 1 file changed, 109 insertions(+), 73 deletions(-) diff --git a/backend/src/lib/gemini.ts b/backend/src/lib/gemini.ts index fe4ffb2..d6f824c 100644 --- a/backend/src/lib/gemini.ts +++ b/backend/src/lib/gemini.ts @@ -1,6 +1,6 @@ import type { Context } from 'hono'; import { env } from '../config/env.js'; -import { GoogleGenerativeAI } from '@google/generative-ai'; +import { GoogleGenerativeAI, SchemaType } from '@google/generative-ai'; const generateTanka = async (originalText: string): Promise => { // Geminiで短歌生成 @@ -16,66 +16,89 @@ const generateTanka = async (originalText: string): Promise => { } const genAI = new GoogleGenerativeAI(apiKey); + + const schema = { + description: '生成される短歌のオブジェクト', + type: SchemaType.OBJECT, + properties: { + line0: { + type: SchemaType.STRING, + description: '短歌の1句目, 日本語で5音節程度', + nullable: false, + }, + line1: { + type: SchemaType.STRING, + description: '短歌の2句目, 日本語で7音節程度', + nullable: false, + }, + line2: { + type: SchemaType.STRING, + description: '短歌の3句目, 日本語で5音節程度', + nullable: false, + }, + line3: { + type: SchemaType.STRING, + description: '短歌の4句目, 日本語で7音節程度', + nullable: false, + }, + line4: { + type: SchemaType.STRING, + description: '短歌の5句目, 日本語で7音節程度', + nullable: false, + }, + yomi0: { + type: SchemaType.STRING, + description: + '短歌の1句目のふりがな(基本的に日本語のひらがなだが、短歌にアルファベット部分があった場合はその部分のみアルファベットで出力)', + nullable: false, + }, + yomi1: { + type: SchemaType.STRING, + description: + '短歌の2句目のふりがな(基本的に日本語のひらがなだが、短歌にアルファベット部分があった場合はその部分のみアルファベットで出力)', + nullable: false, + }, + yomi2: { + type: SchemaType.STRING, + description: + '短歌の3句目のふりがな(基本的に日本語のひらがなだが、短歌にアルファベット部分があった場合はその部分のみアルファベットで出力)', + nullable: false, + }, + yomi3: { + type: SchemaType.STRING, + description: + '短歌の4句目のふりがな(基本的に日本語のひらがなだが、短歌にアルファベット部分があった場合はその部分のみアルファベットで出力)', + nullable: false, + }, + yomi4: { + type: SchemaType.STRING, + description: + '短歌の5句目のふりがな(基本的に日本語のひらがなだが、短歌にアルファベット部分があった場合はその部分のみアルファベットで出力)', + nullable: false, + }, + }, + required: [ + 'line0', + 'line1', + 'line2', + 'line3', + 'line4', + 'yomi0', + 'yomi1', + 'yomi2', + 'yomi3', + 'yomi4', + ], + }; + const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash', - systemInstruction: `SNSの投稿を短歌にしてください。原文の特徴的な要素はそのままに、比喩表現を使った趣深い短歌にしてください。出力の形式は以下のJSONスキーマに従ってください。各値は日本語で出力してください。 - { - "type": "object", - "description": "変換した短歌", - "properties": { - "response": { - "type": "array", - "description": "ユーザーアカウント作成のレスポンスデータの配列", - "items": { - "type": "object", - "properties": { - "line1": { - "type": "string", - "description": "短歌の1句目, 5文字程度" - }, - "line2": { - "type": "string", - "description": "短歌の2句目, 7文字程度" - }, - "line3": { - "type": "string", - "description": "短歌の3句目, 5文字程度" - }, - "line4": { - "type": "string", - "description": "短歌の4句目, 7文字程度" - }, - "line5": { - "type": "string", - "description": "短歌の5句目, 7文字程度" - }, - "yomi1": { - "type": "string", - "description": "短歌の1句目のふりがな" - }, - "yomi2": { - "type": "string", - "description": "短歌の2句目のふりがな" - }, - "yomi3": { - "type": "string", - "description": "短歌の1句目のふりがな" - }, - "yomi4": { - "type": "string", - "description": "短歌の2句目のふりがな" - }, - "yomi5": { - "type": "string", - "description": "短歌の1句目のふりがな" - }, - }, - "required": ["line1", "line2", "line3", "line4", "line5", "yomi1", "yomi2", "yomi3", "yomi4", "yomi5"] + systemInstruction: + 'SNSの投稿を短歌にしてください。原文の特徴的な要素はそのままに、比喩表現を使った趣深い短歌にしてください。出力の形式は指定したJSONスキーマに従ってください。各値は日本語で出力してください。', + generationConfig: { + responseMimeType: 'application/json', + responseSchema: schema, }, - "required": ["response"] - } - }`, - generationConfig: { responseMimeType: 'application/json' }, }); // 短歌の各句の文字数をチェックする関数 @@ -83,54 +106,67 @@ const generateTanka = async (originalText: string): Promise => { const expectedCharaCount = [5, 7, 5, 7, 7]; return lines.every((line, index) => { // everyは配列のすべての要素が条件を満たしていればtrueを返す - //console.log(line); + console.log(line); // アルファベット(全角、半角)、ひらがな、カタカナ、漢字を1文字としてカウント const regex = /[A-Za-z\uFF21-\uFF3A\uFF41-\uFF5A\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]/g; - // console.log(line.match(regex)); + console.log(line.match(regex)); const count = line.match(regex)?.length || 0; - // console.log('count: ', count); - // console.log('-----------------------------'); + console.log('count: ', count); + // 文字数をカウント(アルファベット(全角、半角)、ひらがな、カタカナ、漢字を1文字としてカウント) return Math.abs(count - expectedCharaCount[index]) <= 1; // 1文字分までの誤差は許容 }); }; + const printLine = (): void => { + console.log('--------------------------------'); + }; + // 生成後、型のチェック(3回まで) for (let i = 0; i < 3; i++) { + printLine(); + console.log(`短歌生成${i + 1}回目`); + const result = await model.generateContent(originalText); + + console.log(result.response.text()); + const jsonResponse = JSON.parse(result.response.text()); - console.log(`短歌生成${i + 1}回目`); + console.log(jsonResponse); const tanka = [ - jsonResponse.response[0].line1.replace('ッ', 'ツ'), - jsonResponse.response[0].line2.replace('ッ', 'ツ'), - jsonResponse.response[0].line3.replace('ッ', 'ツ'), - jsonResponse.response[0].line4.replace('ッ', 'ツ'), - jsonResponse.response[0].line5.replace('ッ', 'ツ'), + jsonResponse.line0.replace('ッ', 'ツ'), + jsonResponse.line1.replace('ッ', 'ツ'), + jsonResponse.line2.replace('ッ', 'ツ'), + jsonResponse.line3.replace('ッ', 'ツ'), + jsonResponse.line4.replace('ッ', 'ツ'), ]; const tankaYomi = [ - jsonResponse.response[0].yomi1, - jsonResponse.response[0].yomi2, - jsonResponse.response[0].yomi3, - jsonResponse.response[0].yomi4, - jsonResponse.response[0].yomi5, + jsonResponse.yomi0, + jsonResponse.yomi1, + jsonResponse.yomi2, + jsonResponse.yomi3, + jsonResponse.yomi4, ]; // console.log(tanka); if (isValidTanka(tankaYomi)) { + printLine(); console.log('短歌の形式が正しいので結果を返却'); // ["短歌の1行目", "短歌の2行目", "短歌の3行目", "短歌の4行目", "短歌の5行目"] return tanka; } else if (i < 2) { + printLine(); console.log(tanka); console.log(tankaYomi); console.log('短歌の形式が不正のため再生成'); } } + printLine(); console.log('短歌を生成できませんでした'); throw new Error('短歌を生成できませんでした'); } catch (error) { From d5a8426764085eb24e0642e89675a8c5e14ac9cb Mon Sep 17 00:00:00 2001 From: Yone Date: Wed, 19 Feb 2025 20:18:01 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E3=80=8C=E3=82=83=E3=80=8D=E3=80=8C?= =?UTF-8?q?=E3=83=A3=E3=80=8D=E3=80=8C=E3=82=85=E3=80=8D=E3=80=8C=E3=83=A5?= =?UTF-8?q?=E3=80=8D=E3=80=8C=E3=82=87=E3=80=8D=E3=80=8C=E3=83=A7=E3=80=8D?= =?UTF-8?q?=E3=81=AF=E8=AA=AD=E3=81=BF=E3=81=A8=E3=81=97=E3=81=A6=E3=82=AB?= =?UTF-8?q?=E3=82=A6=E3=83=B3=E3=83=88=E3=81=97=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/lib/gemini.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/lib/gemini.ts b/backend/src/lib/gemini.ts index d6f824c..ed200cf 100644 --- a/backend/src/lib/gemini.ts +++ b/backend/src/lib/gemini.ts @@ -109,8 +109,12 @@ const generateTanka = async (originalText: string): Promise => { console.log(line); // アルファベット(全角、半角)、ひらがな、カタカナ、漢字を1文字としてカウント const regex = /[A-Za-z\uFF21-\uFF3A\uFF41-\uFF5A\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]/g; - console.log(line.match(regex)); - const count = line.match(regex)?.length || 0; + const matchedChars = line.match(regex) || []; + console.log(matchedChars); + // 「ゃ」「ャ」「ゅ」「ュ」「ょ」「ョ」はカウントしない + const excludeChars = ['ゃ', 'ャ', 'ゅ', 'ュ', 'ょ', 'ョ']; + const validChars = matchedChars.filter((char) => !excludeChars.includes(char)); + const count = validChars.length; console.log('count: ', count); // 文字数をカウント(アルファベット(全角、半角)、ひらがな、カタカナ、漢字を1文字としてカウント)