-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.js
265 lines (217 loc) · 10.5 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
const express = require("express");
const path = require("path");
require("dotenv").config();
const CohereClient = require("cohere-ai").CohereClient;
const {
GoogleGenerativeAI, HarmCategory, HarmBlockThreshold
} = require("@google/generative-ai");
const app = express();
const PORT = 3000;
const geminiApiKey = process.env.GEMINI_API_KEY;
const cohereApiKey = process.env.COHERE_API_KEY;
const voicevoxApiKey = process.env.VOICEVOX_API_KEY;
const genAI = new GoogleGenerativeAI(geminiApiKey);
const cohere = new CohereClient({ token: cohereApiKey });
let chatSession;
let theme = "";
let pros = "";
let cons = "";
let insertSummary = "";
let prosAssertion = null;
let consAssertion = null;
// expressミドルウェアの設定
app.use(express.text());
app.use(express.json());
// 起動サーバーのルート指定
app.use("/", express.static(path.join(__dirname, "public")));
// サーバ起動
app.listen(PORT, () => {
console.log(`Server started on port:${PORT}`);
});
// レスポンスを成型する関数
function formatResponseText(responseText, toHTML) {
let text = responseText;
if (toHTML) text = text.replace(/[\n]/g, "<br>");
return text.replace(/[#*]/g, "")
}
// Gemini用初期化関数
function initializeChatSession() {
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
return model.startChat({
generationConfig: {
temperature: 1,
topP: 0.95,
topK: 64,
maxOutputTokens: 8192,
responseMimeType: "text/plain",
},
history: [],
});
}
// 論点整理用関数
async function organizeDebate(chatSession) {
const geminiOrganize = await chatSession.sendMessage(`
次に与えられる「テーマ」から、考えられる「賛成派の主張」および「反対派の主張」をそれぞれ、とても簡潔な一文で定義づけるように表現してください。その一文には絶対に理由や追加の情報を含めないでください。
以下の例を参考に回答を作成してください。
例1
テーマ:時間とお金どちらが大事か
賛成派:時間が大事であり、お金よりも価値がある。
反対派:お金が大事であり、時間よりも重要だ。
例2
テーマ:中高一貫校は必要なのか
賛成派:中高一貫校は必要である。
反対派:中高一貫校は必要ではない。
例3
テーマ:目玉焼きには塩か醤油か
賛成派:目玉焼きには塩の方がしょうゆより適している。
反対派:目玉焼きにはしょうゆの方が塩より適している。
それでは、以下のテーマについて回答を作成してください。
テーマ:${theme}。理由を述べずに、端的に表現して。
`);
const geminiOrganizeRes = formatResponseText(geminiOrganize.response.text(), false);
const parts = geminiOrganizeRes.split("反対派:");
pros = parts[0].split("賛成派:")[1];
cons = parts[1];
return geminiOrganizeRes.replace("賛成派:", "<br>賛成派:").replace("反対派:", "<br>反対派:");
}
// 要約用関数
async function summarizeDebate(chatSession) {
const geminiSummarize = await chatSession.sendMessage(`
以下の議論が行われています。
テーマ:${theme}
賛成派の意見:${pros}
反対派の意見:${cons}
賛成派の見解:${prosAssertion}
反対派の見解:${consAssertion}
上記の賛成派、および反対派の意見・見解を総括したものを、以下の形式に従って回答してください。
賛成派の主張:
「●」の箇条書きで賛成派の主張とその理由を、書き言葉で各項目を200トークン以内で記入してください。
反対派の主張:
「●」の箇条書きで反対派の主張とその理由を、書き言葉で各項目を200トークン以内で記入してください。
`);
return `これまでの議論のまとめ<br>${formatResponseText(geminiSummarize.response.text(), true)}`;
}
// Gemini API Handler
app.post("/api/gemini", async (req, res) => {
chatSession = chatSession || initializeChatSession();
// AI豆打者からの場合
if(req.body.order === -1) {
const geminiQuestion = await chatSession.sendMessage(`${req.body.text}/回答は口語体で、語尾は「なのだ」「のだ」を適宜活用し統一して、200トークン以内にしてください。`);
const geminiQuestionRes = formatResponseText(geminiQuestion.response.text(), false);
if (geminiQuestionRes.includes("A server error has occurred FUNCTION_INVOCATION_FAILED")) {
res.send(badContentNotice);
} else res.send(geminiQuestionRes);
return;
}
// AI討論からの場合
// 論点整理用のとき (orderInt == 0)
if(req.body.order === 0) {
theme = req.body.text;
res.send(await organizeDebate(chatSession));
return;
// 2回目以降はまずサマライズしてから
} else if (req.body.order !== 1) insertSummary = await summarizeDebate(chatSession);
// 既存の立場を基に賛成の意見を出力
const geminiPros = await chatSession.sendMessage(`
以下の議論が行われています。
テーマ:${theme}
賛成派の意見:${pros}
反対派の意見:${cons}
${insertSummary}
上記のテーマについて、賛成派と反対派がそれぞれの主張とその理由を議論します。
あなたは賛成派の立場に立って、新たな視点を用いて、語尾は「なのだ」「のだ」を適宜活用し統一して論理的に主張してください。
根拠となるデータや考察の補足をするようにしてください。
150トークン以内で、箇条書きや改行、マークダウンを絶対に使わないでください。
回答の例
例1:〇〇です。その理由は△△だからです。
例2:□□です。その根拠となるデータが◇◇で示されているからです。
`);
prosAssertion = geminiPros.response.text();
res.send(JSON.stringify({ summary: insertSummary, assertion: prosAssertion }));
});
// Cohere API Handler
app.post("/api/cohere", async (req, res) => {
try {
// 既存の立場を基に反対の意見を出力
const cohereCons = await cohere.chat({
model: "command-r",
message: `
以下の議論が行われています。
テーマ:${theme}
賛成派の意見:${pros}
反対派の意見:${cons}
${insertSummary}
賛成派としての見解:${prosAssertion}
上記のテーマについて、賛成派と反対派がそれぞれの主張とその理由を議論します。
あなたは反対派の立場に立って、新たな視点を用いて、話し言葉で、論理的に主張してください。根拠となるデータや考察の補足をするようにしてください。150トークンで、**箇条書きや改行、マークダウンを絶対に使わないでください**。
反対派としての見解:「**ここの中身のみを書いてください**」`,
});
consAssertion = cohereCons.text;
res.send(consAssertion);
} catch (error) {
// エラーハンドリング
console.error('Error:', error);
}
});
// VOICEVOX local API Handler
app.post("/api/voicevox/local", async (req, res) => {
const apiUrl = "http://localhost:50021";
const intonationScale = 0.7;
const speed = 1.2;
try {
const audioQueryResponse = await fetch(`${apiUrl}/audio_query?text=${encodeURIComponent(req.body.text)}&speaker=${req.body.speaker}`, {
method: "POST",
headers: {
"accept": "application/json",
},
});
if (!audioQueryResponse.ok) {
throw new Error("音声生成(クエリ生成)に失敗しました");
}
const audioQueryData = await audioQueryResponse.json()
audioQueryData.intonationScale = intonationScale;
audioQueryData.speedScale = speed;
const synthesisResponse = await fetch(`${apiUrl}/synthesis?speaker=${req.body.speaker}`, {
method: "POST",
headers: {
"accept": "audio/wav",
"Content-Type": "application/json",
},
body: JSON.stringify(audioQueryData)
});
if (!synthesisResponse.ok) {
throw new Error("音声生成(wav生成)に失敗しました");
}
// 音声バイナリを受け取る
const voicevoxResult = await synthesisResponse.arrayBuffer();
// フロントエンドにBufferに整形して返す
res.set("Content-Type", "audio/wav");
res.send(Buffer.from(voicevoxResult));
} catch (error) {
res.status(500).json({ error: "リクエストに失敗しました" });
console.log(`VOICEVOXローカル版API処理時にエラーが発生しました: ${error.message}\nストリーミング版で合成されます`);
}
});
// VOICEVOX fast version API Handler
app.post("/api/voicevox/fast", async (req, res) => {
const apiUrl = "https://deprecatedapis.tts.quest/v2/voicevox/audio";
const intonationScale = 0.8;
const speed = 1.2;
try {
const response = await fetch(`${apiUrl}?key=${voicevoxApiKey}&speaker=${req.body.speaker}&intonationScale=${intonationScale}&speed=${speed}&text=${req.body.text}`);
if (!response.ok) {
throw new Error("音声生成に失敗しました", response);
}
// 音声バイナリを受け取る
const voicevoxResult = await response.arrayBuffer();
// フロントエンドにBufferに整形して返す
res.set("Content-Type", "audio/wav");
res.send(Buffer.from(voicevoxResult));
} catch (error) {
res.status(500).json({ error: "リクエストに失敗しました" });
console.log(`VOICEVOX高速版API処理時にエラ―が発生しました: ${error.message}\nストリーミング版で合成されます`);
}
});
// VOICEVOX streaming API Handler
app.post("/api/voicevox/streaming", (req, res) => res.send(voicevoxApiKey));
module.exports = app;