Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yone/develop #88

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 114 additions & 160 deletions backend/src/lib/gemini.ts
Original file line number Diff line number Diff line change
@@ -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<string[]> => {
// Geminiで短歌生成
Expand All @@ -16,121 +16,161 @@ const generateTanka = async (originalText: string): Promise<string[]> => {
}

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' },
});

// 短歌の各句の文字数をチェックする関数
const isValidTanka = (lines: string[]): boolean => {
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));
const count = line.match(regex)?.length || 0;
// console.log('count: ', count);
// console.log('-----------------------------');
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文字としてカウント)
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) {
Expand All @@ -139,90 +179,4 @@ const generateTanka = async (originalText: string): Promise<string[]> => {
}
};

/*
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;
Loading