300行のHTMLファイル…ヒントページ

仕組みや原理の理解は大事にして欲しいのですが、

手段でしかないJavaScriptコードの書き方等で苦労してもらうのは本望ではないので、各課題のヒントを記載していきます。

課題1 - 作業1

問題ないと思うので記載省略。

課題1 - 作業2

正規表現を利用してカンマを改行に変換するのが早い。

今回は、カンマの後に半角スペースがあるので、「, 」を改行コードに置換すればOK

Visual Studio Code での例

改行コードは「\n」で指定。「\r\n」だと「\r」が改行コードではなく、文字として認識される模様。

image

サクラエディタでの例

改行コードは「\r\n」で指定。

image

課題1 - 作業3

Excel作業で、問題ないと思うので記載省略。

課題1 - 作業4 / 作業5

こちらもExcel作業で、問題ないと思うので記載省略。

課題2 - 作業1

問題ないと思うので記載省略。

課題2 - 作業2

問題ないと思うので記載省略。

課題2 - 作業3

Script部分のコード例を記載します。

1番目のHTMLファイルをベースに、2番目のHTMLファイルから「uploadData」関数を流用すれば作成可能です。

	<script>
		const openAiApiKey = '個々人で異なる';
		const urlEmbedding = 'https://api.openai.com/v1/embeddings';

		const pineconApiKey = '個々人で異なる';
		const urlVectorDbIndex = '個々人で異なる';
		const urlVectorDbUl = urlVectorDbIndex + '/vectors/upsert'

		async function execute() {
			const text = document.getElementById('originalText').value;
			const vector = await getEmbedding(text);
			document.getElementById('vectorData').textContent = vector;

			const userInput = document.getElementById('vectorData').value;
			const vectorArray = userInput.split(',').map(Number);
			const originalText = document.getElementById('originalText').value;

			const result = await uploadData(vectorArray, originalText);

			document.getElementById('response').innerText = result; 
		}

		async function getEmbedding(text) {
			const data = {
				input: text,
				model: 'text-embedding-ada-002'
				//model: 'text-embedding-3-large'
			};

			const response = await fetch(urlEmbedding, {
				method: 'POST',
				headers: {
				'Content-Type': 'application/json',
				'Authorization': `Bearer ${openAiApiKey}`
				},
				body: JSON.stringify(data)
			});

			const result = await response.json();
			const embedding = result.data[0].embedding; // JSONから必要データを抽出
			return embedding.map(item => JSON.stringify(item)).join(', '); // 後工程用にカンマ区切りでベクトル値を表現
		}

		async function uploadData(vector, txt) {
			const Payload = {
				vectors: [{
					id: Date.now().toString(), // レコードID値は1970/1/1からの経過ミリ秒数
					values: vector,
					metadata: { text: txt }
				}]
			};

			const response = await fetch(urlVectorDbUl, {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json',
					'Api-Key': pineconApiKey
				},
				body: JSON.stringify(Payload)
			});

			const data = await response.json();
			return '結果: ' + JSON.stringify(data);
		}
	</script>

課題3

特に問題ないと思うので省略。

課題4

Script部分のコード例を記載します。

凝った作りにすることもできる箇所ですが、サンプルとして分かり易そうなコードにしました。

ハマる人が多そうなのは、Pineconeから取得したデータから、テキスト部分(例.:「私の性格は***です」「Xさんの性格***です」)を抽出する処理かと思います。

let privateData = await getPrivateJson(vector); privateData = privateData.matches.map(match => match.metadata.text); // text項目のみ抽出
	<script>
		// APIキー
		const openAiApiKey = '個々人で異なる';
		const pineconApiKey = '個々人で異なる';

		// APIのURL:openAI
		const urlEmbedding = 'https://api.openai.com/v1/embeddings';
		const urlAiChat =  'https://api.openai.com/v1/chat/completions';

		// APIのURL:Pinecone
		const urlVectorDbIndex = '個々人で異なる';
		const urlVectorDbQuery = urlVectorDbIndex + '/query'

		const chatBox = document.getElementById('chat-box');
		const userInput = document.getElementById('user-input');

		// 重要処理1:システムプロンプト設定
		let messages = [{ 
			role: "system", 
			content: "あなたはRAGシステムのAIアシスタントです。ユーザーが入力した文章は「#ユーザー入力文:」の後に設定し、ベクトルデータベースから取得する「入力文とベクトル値が近似するプライベート情報」は「#プライベート情報:」の後に設定しますので、2つの情報を加味した回答を作成してください。" 
		}];

		async function sendMessage() {
			document.getElementById('user-input').blur(); // スマホのキーボード表示を解除

			const message = userInput.value.trim();
			if (message === "") return;

			appendMessage(message, 'user-message');
			userInput.value = "";

			try {
				// 重要処理2:入力文のベクトル値を取得
				const vector = await getEmbedding(message);
				console.log('埋め込みベクトル:', vector);

				// 重要処理3:ベクトルが近似するプライベート情報を取得
        let privateData = await getPrivateJson(vector);
        privateData = privateData.matches.map(match => match.metadata.text); // text項目のみ抽出
        console.log('プライベートデータ:', privateData);

				// 重要処理4:プライベート情報を含めたユーザープロンプトを作成
				messages.push({
					role: "user", content: "#ユーザー入力文:" + message + "\n #プライベート情報:" + privateData
				});

				// 重要処理5:ChatGPTへの通信&回答表示
				const bodyRaw = JSON.stringify({
					model: "gpt-4o",
					messages: messages,
				});

				const response = await fetch(urlAiChat, {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json',
						'Authorization': `Bearer ${openAiApiKey}`
					},
					body: bodyRaw
				});

				const data = await response.json();
				const botMessages = data.choices.map(choice => choice.message.content);

				botMessages.forEach(botMessage => {
					appendMessage(botMessage, 'bot-message');
					messages.push({
						role: "assistant",
						content: [
							{
								type: "text",
								text: botMessage
							}
						]
					});

					console.log(messages);
				});
			} 
			catch (error) {
				appendMessage('エラーが発生しました。', 'bot-message');
			}
		}

		function appendMessage(text, className) {
			const messageElement = document.createElement('div');
			messageElement.className = `message ${className}`;
			messageElement.textContent = text;
			chatBox.appendChild(messageElement);
			chatBox.scrollTop = chatBox.scrollHeight;
		}

		// 関数記述部~重要処理2:入力文のベクトル値を取得~
		async function getEmbedding(text) {
			const data = {
			input: text,
			model: 'text-embedding-ada-002'
			};

			const response = await fetch(urlEmbedding, {
				method: 'POST',
				headers: {
				'Content-Type': 'application/json',
				'Authorization': `Bearer ${openAiApiKey}`
				},
				body: JSON.stringify(data)
			});

			const result = await response.json();
			const embedding = result.data[0].embedding;
			return embedding.map(item => JSON.stringify(item)).join(', '); //カンマ区切りに整形
		}

	   // 関数記述部~重要処理3:ベクトルが近似する情報を取得~
	   async function getPrivateJson(input) {
			const vector = input.split(',').map(Number); //配列として格納

			const response = await fetch(urlVectorDbQuery, {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json',
					'Api-Key': pineconApiKey
				},
				body: JSON.stringify({
					"vector": vector,
					"topK": 5, // 取得レコード数の指定だが、類似度が高いレコードが少ない場合、指定数を下回るレコード数しか取得できない
					"includeMetadata": true // ベクトル化前の文章はメタデータ(付属情報)として保存されている
				})
			});

			return await response.json();
		}

		function resetChat() {
			document.getElementById('chat-box').innerHTML = "";
			messages.splice(1); //システムプロンプト設定のみ残す
		} 
	</script>

課題5

特に問題ないと思うので省略。

課題6

特に問題ないと思うので省略。