2024年09月 - 考えるエンジニア
FC2ブログ

Semantic Kernelを使う(4)

 前回のブログから時間が経ちました。久々にSemantic Kernelを触ってみようと思い立ちました。RAG(Retrieval-Augmented Generation)の技術に大変興味があり、以前のブログでLangchainLlamaIndexを使ったことがありましたが、今回はSemantic KernelのPlugins機能を使いました。

 Plugins機能は、MicrosoftのWebサイト情報によると以下の通り。Plugins_240928.png「関数呼び出しを使用すると、LLMは特定の関数を要求(つまり、呼び出し)できます。次に、Semantic Kernelは、コードベース内の適切な関数に要求をマーシャリングし、結果をLLMに返して、LLMが最終応答を生成できるようにします。」

 プログラム例があまりない中で、分かりやすく解説したサイトがあったので、参考にさせていただきました。ありがとうございました。

 Visual Studio 2022でC#コンソールアプリのソリューション(SkTest1.sln)を作成しました。各ファイルの構成は以下の通りです。Solution_240928.png参考サイトには元々「今日の日付を取得する関数(クラス)」の「DateTimePlugin.cs」がありましたが、今回「PricePlugin.cs」と「price.csv」を追加しました。
 それぞれのプログラムは以下の通りです。
//Program.cs
using SkTest1.Plugin;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;

var builder = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: "My deploymentName",
endpoint: "My endpoint",
apiKey: "My apiKey"
);

Kernel kernel = builder.Build();
kernel.Plugins.AddFromType<DateTimePlugin>();
kernel.Plugins.AddFromType<PricePlugin>();

var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};

var history = new ChatHistory();

string? userInput;
do
{
Console.Write("User > ");
userInput = Console.ReadLine();

if (string.IsNullOrEmpty(userInput))
{
continue;
}

history.AddUserMessage(userInput);

var result = await chatCompletionService.GetChatMessageContentAsync(
history,
executionSettings: openAIPromptExecutionSettings,
kernel: kernel);

Console.WriteLine("Assistant > " + result);

// Add the message from the agent to the chat history
history.AddMessage(result.Role, result.Content ?? string.Empty);
} while (userInput is not null);
//DateTimePlugin.cs
using Microsoft.SemanticKernel;
using System.ComponentModel;

namespace SkTest1.Plugin
{
internal class DateTimePlugin
{
[KernelFunction("get_current_date")]
[Description("Get current date and time")]
[return: Description("Return current date and time with formatting conventions of the current culture")]

public async Task<string> GetCurrentDateAsync()
{
return DateTime.Now.Date.ToString("yyyy/MM/dd HH:mm:ss");
}
}
}
 今回追加した「price.csv」は、ある定食屋の料理とその金額のデータで、中身は以下の通りです。
日替わり定食,750
唐揚げ定食,800
ロースかつ定食,1000
カレーライス,600
カツカレー,900
かけうどん,500
きつねうどん,650
親子丼,750
かつ丼,880
また、「PricePlugin.cs」は、事前に準備した、このcsvファイルを読み込み、料理名を入力するとその金額を返す関数です。
//PricePlugin.cs
using Microsoft.SemanticKernel;
using System.ComponentModel;

namespace SkTest1.Plugin
{
internal class PricePlugin
{
[KernelFunction("get_food_price")]
[Description("Get food price from data file")]
[return: Description("Return food price")]

public async Task<string> GetFoodPriceeAsync(string foodname)
{
// CSVファイルのパス
string filePath = "C:\\Users\\drbobt\\source\\repos\\SkTest1\\SkTest1\\Plugin\\price.csv";

// 料理名と価格を格納するディクショナリ
Dictionary<string, int> priceList = new Dictionary<string, int>();

// CSVファイルを読み込む
try
{
using (StreamReader sr = new StreamReader(filePath, System.Text.Encoding.GetEncoding("utf-8")))
{
string? line;
while ((line = sr.ReadLine()) != null)
{
// 行を分割して料理名と価格を取得
string[] parts = line.Split(',');
if (parts.Length == 2)
{
string foodName = parts[0];
int price = int.Parse(parts[1]);
priceList[foodName] = price;
//Console.WriteLine(foodName + " " + price.ToString());
}
}
}
}
catch (Exception e)
{
Console.WriteLine("ファイルの読み込み中にエラーが発生しました: " + e.Message);
return "読み込みエラー";
}
return priceList[foodname].ToString();
}
}
}

 実行結果です。Answer_plugin_240928.png 今日(9/28:土曜日)実行して、明日を正しく9/29と認識し、かつ、「かけうどん(500円)とカツカレー(900円)」の合計の1,400円を見事に回答できました。また、来週の月曜日の9/30も正しく回答できました。
 念のため、通常のGPT-4oで同じプロンプトを投げてみました。Answer_gpt4o_240928.pngさすがに定食屋の情報がないので、回答できず。ただ、明日が9/29と回答したので、日時を把握しているのか一瞬驚きましたが、来週の月曜日の日付は誤答でした。ただ、今日の曜日は正解だったので、今日の情報は何らかの形でGPT-4oは持っているのかもしれません(確証はありませんが・・)。

 今回、Semantic KernelのPlugins機能を使ってみました。Semantic Kernelは開発が盛んで仕様が今後も更新されることがあると思われるので、ウォッチが必要ですが、RAGをC#で利用するのに必要な技術なので、引き続き勉強して行こうと思います。

生成AIでアプリ開発をする(9)

 このシリーズも今回で最終回としたいと思います。シリーズ2回目で「Form画面にオブジェクトを手作業で作り込んでいくのも面倒だ。プロンプトを駆使すれば解決しそうな気がする。」と話していました。今回、実際の検証を行いました。つまり、「*.Designer.cs」ファイルを漫画絵から作成する試みです。Form_image_240923.png
 さっそくプロンプトを実行しました。実際のプロンプトは英語で記載しました。
添付の図を参考に、以下のコンポーネントを含む C# Windows Forms Designer ファイルを作成してください。

"Drop a file here"(ファイルをここにドロップ)とラベル付けされたファイルをドラッグ&ドロップするためのパネル(位置:左上)。
パネルの下に、ファイル読み込み後にファイル名を表示するためのテキストボックス。
右側に縦に並んだ3つのボタン:
「画像変換」(PDFを画像に変換します。)
「文字認識」(文字起こしを行う)
「結果保存」(結果をファイルに保存)
フォームの下部には、転記結果を表示・編集するための複数行のテキストボックスがあります。
ドラッグ&ドロップパネルの上に「文書を添付」、テープ起こし結果のテキストボックスの上に「出力」など、必要に応じてラベルを追加します。

 実行してGPT-4oが出力した結果(Chareco.Designer.cs)は以下の通りです。
namespace Chareco
{
partial class Chareco
{
private System.ComponentModel.IContainer components = null;

protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code

private void InitializeComponent()
{
this.panelDrop = new System.Windows.Forms.Panel();
this.labelAttach = new System.Windows.Forms.Label();
this.textBoxFileName = new System.Windows.Forms.TextBox();
this.buttonImageConversion = new System.Windows.Forms.Button();
this.buttonCharacterRecognition = new System.Windows.Forms.Button();
this.buttonResultSave = new System.Windows.Forms.Button();
this.textBoxOutput = new System.Windows.Forms.TextBox();
this.labelOutput = new System.Windows.Forms.Label();

//
// panelDrop
//
this.panelDrop.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panelDrop.Location = new System.Drawing.Point(12, 35);
this.panelDrop.Name = "panelDrop";
this.panelDrop.Size = new System.Drawing.Size(220, 100);
this.panelDrop.TabIndex = 0;

//
// labelAttach
//
this.labelAttach.AutoSize = true;
this.labelAttach.Location = new System.Drawing.Point(12, 15);
this.labelAttach.Name = "labelAttach";
this.labelAttach.Size = new System.Drawing.Size(105, 15);
this.labelAttach.TabIndex = 1;
this.labelAttach.Text = "Attach a document";

//
// textBoxFileName
//
this.textBoxFileName.Location = new System.Drawing.Point(12, 145);
this.textBoxFileName.Name = "textBoxFileName";
this.textBoxFileName.Size = new System.Drawing.Size(220, 23);
this.textBoxFileName.TabIndex = 2;

//
// buttonImageConversion
//
this.buttonImageConversion.Location = new System.Drawing.Point(250, 35);
this.buttonImageConversion.Name = "buttonImageConversion";
this.buttonImageConversion.Size = new System.Drawing.Size(130, 30);
this.buttonImageConversion.TabIndex = 3;
this.buttonImageConversion.Text = "Image Conversion";
this.buttonImageConversion.UseVisualStyleBackColor = true;

//
// buttonCharacterRecognition
//
this.buttonCharacterRecognition.Location = new System.Drawing.Point(250, 75);
this.buttonCharacterRecognition.Name = "buttonCharacterRecognition";
this.buttonCharacterRecognition.Size = new System.Drawing.Size(130, 30);
this.buttonCharacterRecognition.TabIndex = 4;
this.buttonCharacterRecognition.Text = "Character Recognition";
this.buttonCharacterRecognition.UseVisualStyleBackColor = true;

//
// buttonResultSave
//
this.buttonResultSave.Location = new System.Drawing.Point(250, 115);
this.buttonResultSave.Name = "buttonResultSave";
this.buttonResultSave.Size = new System.Drawing.Size(130, 30);
this.buttonResultSave.TabIndex = 5;
this.buttonResultSave.Text = "Result Save";
this.buttonResultSave.UseVisualStyleBackColor = true;

//
// textBoxOutput
//
this.textBoxOutput.Location = new System.Drawing.Point(12, 200);
this.textBoxOutput.Multiline = true;
this.textBoxOutput.Name = "textBoxOutput";
this.textBoxOutput.Size = new System.Drawing.Size(368, 120);
this.textBoxOutput.TabIndex = 6;

//
// labelOutput
//
this.labelOutput.AutoSize = true;
this.labelOutput.Location = new System.Drawing.Point(12, 180);
this.labelOutput.Name = "labelOutput";
this.labelOutput.Size = new System.Drawing.Size(44, 15);
this.labelOutput.TabIndex = 7;
this.labelOutput.Text = "Output";

//
// Chareco
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(392, 341);
this.Controls.Add(this.labelOutput);
this.Controls.Add(this.textBoxOutput);
this.Controls.Add(this.buttonResultSave);
this.Controls.Add(this.buttonCharacterRecognition);
this.Controls.Add(this.buttonImageConversion);
this.Controls.Add(this.textBoxFileName);
this.Controls.Add(this.labelAttach);
this.Controls.Add(this.panelDrop);
this.Name = "Chareco";
this.Text = "Chareco";
this.ResumeLayout(false);
this.PerformLayout();
}

#endregion

private System.Windows.Forms.Panel panelDrop;
private System.Windows.Forms.Label labelAttach;
private System.Windows.Forms.TextBox textBoxFileName;
private System.Windows.Forms.Button buttonImageConversion;
private System.Windows.Forms.Button buttonCharacterRecognition;
private System.Windows.Forms.Button buttonResultSave;
private System.Windows.Forms.TextBox textBoxOutput;
private System.Windows.Forms.Label labelOutput;
}
}
 Visual Studioを立ち上げて、出力されたコードをコピペして確認すると、こんな感じでした。Form_image_generation240923.png
以下の2点がスケッチと内容と異なりますが、大きな問題ではありませんね。
・左上の四角枠内に"Drop a file here"の文字がない。
・右側のボタン3つの真ん中の「Character Recognition」の文字が消えている(実際は文字は消えておらず、ボタンの長さが足りないため表示されていない)

 プロンプトの指示により、Form画面へのオブジェクト形成も自動化できることが分かりました。またの機会の「生成AIによるアプリ開発」に役立てていこうと思います。
 長い間、お付き合いいただきありがとうございます。このシリーズは一旦終わりますが、別の企画で生成AIをフルに利用して行こうと思います。

生成AIでアプリ開発をする(8)

 前回の続きです。プログラムのパーツがすべて揃いましたので、最終形態のアプリにしようと思います。

最終的に落ち着いた形は、以下の通りです。
 ① PDFファイルの各ページをSpire PDF(無償版)で画像ファイルにする。
 ② ①の画像をAzure AI VisionのOCR機能(無償範囲利用)で文字起こしをする。

最終テストの題材は前回のブログ記事をブラウザで表示した後にPDFファイルにしたものです。blog_pic_240923.pngプログラムは以下の通りです。
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Text;
using Spire.Pdf;
using Spire.Pdf.Graphics;
using Microsoft.Azure.CognitiveServices.Vision.ComputerVision;
using Microsoft.Azure.CognitiveServices.Vision.ComputerVision.Models;
using System.Diagnostics;
using Microsoft.VisualBasic.Devices;
using static System.Net.Mime.MediaTypeNames;

namespace Chareco_c
{
public partial class Chareco_c : Form
{
private string pdfFilePath;
private string imageFilePath;
private string[] imagePageName = new string[100]; //Max page:100
private int imagePageCount;

public Chareco_c()
{
InitializeComponent();
SetupDragDrop();
}

private void SetupDragDrop()
{
dropPanel.AllowDrop = true;
dropPanel.DragEnter += new DragEventHandler(DropPanel_DragEnter);
dropPanel.DragDrop += new DragEventHandler(DropPanel_DragDrop);
}

private void DropPanel_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy;
}

private void DropPanel_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files.Length > 0 && Path.GetExtension(files[0]).ToLower() == ".pdf")
{
pdfFilePath = files[0];
fileNameTextBox.Text = pdfFilePath;
}
}

private void ImageConversionButton_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(pdfFilePath))
{
MessageBox.Show("Please load a PDF file first.");
return;
}

try
{
// Convert PDF to image
imageFilePath = Path.ChangeExtension(pdfFilePath, ".png");
ConvertPdfToImage(pdfFilePath);
MessageBox.Show("PDF converted to image successfully."+ Environment.NewLine + "Page count: " + imagePageCount.ToString());
}
catch (Exception ex)
{
MessageBox.Show($"Error converting PDF to image: {ex.Message}");
}
}

private void ConvertPdfToImage(string pdfPath)
{
//Load a sample PDF document
Spire.Pdf.PdfDocument pdf = new Spire.Pdf.PdfDocument();
pdf.LoadFromFile(pdfPath);

//Page number
imagePageCount = pdf.Pages.Count;

//Loop through each page of the PDF
for (int i = 0; i < imagePageCount; i++)
{
//Convert all pages to images and set the dpi of the images.
System.Drawing.Image image = pdf.SaveAsImage(i, PdfImageType.Bitmap, 240, 240);

//Save images in PNG format in a designated folder
imagePageName[i] = string.Format(@"Pic_" + "ToImage-{0}.png", i);
Debug.WriteLine(imagePageName[i]);
image.Save(imagePageName[i], ImageFormat.Png);
}
}

private async void CharacterRecognitionButton_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(imagePageName[0]))
{
MessageBox.Show("Please convert the PDF to an image first.");
return;
}

try
{
for (int i = 0; i < imagePageCount; i++)
{
Debug.WriteLine(imagePageName[i]+"processing..");
string recognizedText = await PerformOCR(imagePageName[i]);
outputTextBox.AppendText(recognizedText);
}

}
catch (Exception ex)
{
MessageBox.Show($"Error performing character recognition: {ex.Message}");
}
}

private async Task<string> PerformOCR(string imagePath)
{

string key = "My key";
string endpoint = "My endpoint";

ComputerVisionClient client = Authenticate(endpoint, key);

// Read text from URL
var textHeaders = await client.ReadInStreamAsync(File.OpenRead(imagePath));
// After the request, get the operation location (operation ID)
string operationLocation = textHeaders.OperationLocation;
Thread.Sleep(2000);

// Retrieve the URI where the recognized text will be stored from the Operation-Location header.
// We only need the ID and not the full URL
const int numberOfCharsInOperationId = 36;
string operationId = operationLocation.Substring(operationLocation.Length - numberOfCharsInOperationId);

// Extract the text
ReadOperationResult results;
Debug.WriteLine($"Reading text from local file {Path.GetFileName(imagePath)}...");

do
{
results = await client.GetReadResultAsync(Guid.Parse(operationId));
}
while ((results.Status == OperationStatusCodes.Running ||
results.Status == OperationStatusCodes.NotStarted));

var textUrlFileResults = results.AnalyzeResult.ReadResults;
StringBuilder allText = new StringBuilder();

foreach (ReadResult page in textUrlFileResults)
{
foreach (Line line in page.Lines)
{
if (!line.Text.Contains("Evaluation Warning :"))
{
allText.AppendLine(line.Text);
}
}
}
string combinedText = allText.ToString();

return combinedText;
}

public static ComputerVisionClient Authenticate(string endpoint, string key)
{
ComputerVisionClient client =
new ComputerVisionClient(new ApiKeyServiceClientCredentials(key))
{ Endpoint = endpoint };
return client;
}

private void ResultSaveButton_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(outputTextBox.Text))
{
MessageBox.Show("No text to save.");
return;
}

SaveFileDialog saveFileDialog = new SaveFileDialog
{
Filter = "Text files (*.txt)|*.txt",
DefaultExt = "txt",
AddExtension = true
};

if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
File.WriteAllText(saveFileDialog.FileName, outputTextBox.Text);
MessageBox.Show("Text saved successfully.");
}
}

}
}
 当初、生成AIが作成したプログラムに対し、試行錯誤し、追加・修正した箇所は以下の関数部分2か所です。

1. ConvertPdfToImage関数
 画像化する際のdpi(Dots per inch)を設定する所があります。以下の部分です。
//Convert all pages to images and set the dpi of the images.
System.Drawing.Image image = pdf.SaveAsImage(i, PdfImageType.Bitmap, 240, 240);
何水準か変化させるとこんな感じです。DPI_check_1_240923.pngこれらのついて文字認識をすると、当然のことながらdpiの値が小さい時は誤認識が発生しました。また、dpiを大きくすると処理時間が長くなるので、運用上適切な所に落ち着ける必要があります。今回は、240dpiに設定しました。DPI_check_2_240923.png
2. PerformOCR関数
 Spire PDFでPDFファイルを画像化した際に、画像の一番上に、無償版にありがちな「Evaluation Warning: The version can be used only for evaluation purpose...」という「警告」が出ていますが、これについては、文字起こしした後に以下の方法で無視して出力しました。
foreach (Line line in page.Lines)
{
if (!line.Text.Contains("Evaluation Warning :"))
{
allText.AppendLine(line.Text);
}
}

 以下、実際の流れです。PDFファイルをドラッグ&ドロップすると、テキストボックスに読み込まれたPDFファイル名が表示されます。appRun_1_240923.png「Image Conversion」ボタンを押すと、PDFファイルの各ページが画像化されます。今回は4ページ分、画像化されました。appRun_2_240923.png次に「Character Recognition」ボタンを押すと、画像ファイルを文字起こしします。文字起こし結果はOutputのテキストボックスに表示されます。今回は4ページ分連続で文字起こしを行い、追加形式でテキストボックスに出力されました。appRun_3_240923.pngその後、「Result Save」ボタンを押すと、ファイルダイアログが立ち上がりますので、出力ファイル名を記入して、「保存」ボタンを押すとappRun_4_240923.png無事に保存ができました。appRun_5_240923.png出力結果は4ページ分すべて問題ありませんでした。文字起こし結果です。blog_res_240923.png
 生成AIを利用して「PDFファイルの文字起こしアプリ」を作成してきました。要所要所の処理関数については個別にプロンプトを与えて作ってもらう方が良さそうな気がしていますが、大まかな処理の流れは瞬時に作ってくれました。プロンプト次第で何とか完成度の高い自動生成も可能になると考えています。次回は最終回でGUIの自動作成を考えてみます。

生成AIでアプリ開発をする(7)

 前回、画像から文字起こしを行う際に、GPTを利用しましたが、試した方法は画像サイズに制約があることが分かりました。今回は別のアプローチとしてAzureの「Computer Vision」を用いた方法についてお話しします。

 3年半以上前のブログにも書きましたが、当時のCognitive Serviceの中のComputer Visionの「OCR機能」を試したことがあります。ちょうどその機能が日本語に対応した時期でした。結果はTessaractと同程度の性能で、誤認識が多い印象でした。日本語の文字起こしの難しさを当時は実感しました。

 今回、そのブログと同じ画像を現在のComputer Visionにかけてみました。時間も経っているので、Computer Visionの実行環境は新たにデプロイし直し、実行プログラムもgithubにあるものを利用しました。その結果、誤認識は「ほぼゼロ」でした。1か所、AI(アイ)をAl(エル)と認識だけでした。recheck_1_240921.pngrecheck_2_240921.png
 さて、本題に戻って、前回画像サイズの制約で文字起こしを断念した画像についても再確認しましたが、問題なしでした。recheck_3_240921.png
 もっと大きなサイズの画像として、Webサイトの記事をPDFファイル保存して、Spire PDFを用いて画像化し、その画像をComputer Visionにかけて文字起こしをしました。画像サンプルは以下の通り。Pic_ToImage-0_240921.pngプログラムを実行すると、正常に文字起こしができました。res_240921.png
 今回、AzureのComputer Visionを使った文字起こしのトライアルを行いました。3年半前に比べ、当然といえば当然ですが、誤認識がほぼなくなっており、かつ大きなサイズの画像を処理しても正常に出力できることが分かりました。費用面でも無償範囲で利用できる部分もあるので、有効に活用を検討していこうと思います。

生成AIでアプリ開発をする(6)

 前回、PDFファイルをSpire PDFを用いることで画像化できることが分かりました。今回は次のステップとして、画像を使った「文字起こし」にトライしたいと思います。
 今回、画像を添付したプロンプトをGPTに投げて文字起こしをするプログラムの作成を行いましたが大変苦戦しました。以下、その備忘録です。
 画像添付はブラウザ上ではよく行うのですが、そもそも、プログラム上でAPIを使って画像を添付する方法を試したことがなかったので、ネット上で情報を収集しながら進めました。

 実行環境はAzureOpenAIなので、ネット情報を元に、以下のコマンドでAzure.AI.OpenAIパッケージをインストールしました。
dotnet add package Azure.AI.OpenAI --prerelease
 ここでインストールされたVersionは「2.0.0-beta.5」だったのですが、それが悲劇?の始まりでした。Versionが1.0.0から2.0.0に変わっただけで、コードの書き方が変更になっており、ネット情報に2.0.0の情報が少なく(うまく検索でヒットしない・・)、試行錯誤していました。何度もエラーを吐き、その対応もよく分からず、結局うまく行かずに最終的に「1.0.0-beta.17」に戻しました。その気付かない間にAzureOpenAIの利用料が数百円ほど消費していました(生みの苦しみ・・)。

 参考サイトを元にして、実際に正常に動いたプログラムはこんな感じになりました。画像をbase64で文字列にエンコードして、プロンプトと一緒に投げる形です。試行錯誤していた時は、「gptー4」を使っていたのですが、費用削減のために「gpt-4o」に切り替えました。半額になりますので。

 gpt-4: $10.00/1M input tokens, $30.00/1M output tokens
 gpt-4o: $5.00/1M input tokens, $15.00/1M output tokens
using Azure;
using Azure.AI.OpenAI;
using Azure.Identity;
using System;
using System.IO;
using System.Text;

var imagePath = @"/Users/drbobt/Documents/dotNET/gpt4o_test1/sample.png";
Uri ToDataUrl(string filePath, string contentType) => new($"data:{contentType};base64,{Convert.ToBase64String(File.ReadAllBytes(filePath))}");

// gpt-4o
OpenAIClient client = new OpenAIClient(
new Uri("My Endpoint"),
new AzureKeyCredential("My API key"));

var chatCompletionsOptions = new ChatCompletionsOptions()
{
DeploymentName = "My deploy modelname",
Messages =
{
new ChatRequestSystemMessage("You are an AI assistant that helps people find information."),
new ChatRequestUserMessage(
new ChatMessageTextContentItem("画像を説明してください。"),
new ChatMessageImageContentItem(ToDataUrl(imagePath, "image/jpeg")))
},
MaxTokens = 4096
};

Response<ChatCompletions> response = await client.GetChatCompletionsAsync(chatCompletionsOptions);
ChatResponseMessage responseMessage = response.Value.Choices[0].Message;
Console.WriteLine($"応答: {responseMessage.Content}");
テストとして、以下の画像を説明してくださいとプロンプトを出しました。sample_240921.png回答は以下の通りです。正常に画像添付したプロンプトがgpt-4oに送られ、回答を得ることが出来ました。Ans_sample_240921.png
 次に、本題の文字認識です。プロンプトの文章を「文字起こしをしてください。」に変更して、実行させました。
var chatCompletionsOptions = new ChatCompletionsOptions()
{
DeploymentName = "My deploy modelname",
Messages =
{
new ChatRequestSystemMessage("You are an AI assistant that helps people find information."),
new ChatRequestUserMessage(
new ChatMessageTextContentItem("文字起こしをしてください。"),
new ChatMessageImageContentItem(ToDataUrl(imagePath, "image/jpeg")))
},
MaxTokens = 4096
};
サンプル画像は私のブログの中の文面です。sample1_240921.png いざ、実行すると以下のエラーが出ました。
 Unhandled exception. System.UriFormatException: Invalid URI: The Uri string is too long.
参考サイトによると、「Uri クラスで扱う URL は 65519 バイト以下である必要があるので、大きな画像は Uri クラスを利用している限り扱えません。」とのことなので、画像サイズを小さくする、あるいは画質を落とす必要が出てきました。結局、今回の場合、50kB程度の画像でもNGだったので、30kB程度の画像で検証しました。元画像(123kB)の一部を抜粋して、かつjpegに画像圧縮して23kBにしました。sample2_240921.jpg結果、100点満点の文字起こしができず、誤認識が発生しました。うまく行かないものです・・。Ans_sample2_240921.pngちなみに、元画像をWeb版のgpt-4oにかけてみると、問題なく正常に文字起こしが出来ました。Web_version_check_240921.png参考サイトに書かれてある通り、大きな画像は、パブリックアクセス可能なBlobを用意してURLを指定して画像を渡した方が良さそうですね。

 今回、画像を添付して、gpt-4oに文字起こしのプロンプトを投げてみました。今回試した方法は、添付する画像サイズに制約があることが分かったので、別の方法を検討する必要があります。次回はその別のアプローチについてお話しします。

生成AIでアプリ開発をする(5)

 前回iTextを使ってPDFファイルの文字起こしを試みましたが、「文章を含んだ画像をPDF印刷したもの」に対応できませんでした。そのため、PDFファイルを一旦画像化した後に、その画像について文字認識で文字起こしをする当初の方針に戻しました。

 ネット上で情報収集をしていると、「Spire PDF」というものが利用できそうなので、さっそく試してみました。

 まず、Visual Studio上でSpire PDFが利用できる環境をセットアップしました。「NuGetパッケージの管理」で、検索窓に「Spire」を記入して、「Spire.PDF」を選択しました。「インストール」ボタンを押すと、SpirePDF_setup1_240917.png変更のプレビューが表示されるので「適用」をクリックすると、SpirePDF_setup2_240917.png正常にセットアップできました。ソリューションエクスプローラのパッケージ部分に登録されていることを確認できました。SpirePDF_setup3_240917.png
 プログラムの追加箇所は前回のプログラムの「ConvertPdfToImage関数」のみで、PDFファイルをページ毎に画像ファイル化するものです。
 ヘッダ部分に以下の2行を追加し、
using Spire.Pdf;
using Spire.Pdf.Graphics;
「ConvertPdfToImage関数」を以下の通り、編集しました。
private void ConvertPdfToImage(string pdfPath, string outputPath)
{
// Placeholder for PDF to image conversion
// You'll need to implement this using a PDF rendering library

//サンプルPDF文書を読み込む
Spire.Pdf.PdfDocument pdf = new Spire.Pdf.PdfDocument();
pdf.LoadFromFile(pdfPath);

//PDFの各ページをループする
for (int i = 0; i < pdf.Pages.Count; i++)
{
//すべてのページを画像に変換し、画像のDpiを設定する。
Image image = pdf.SaveAsImage(i, PdfImageType.Bitmap, 500, 500);

//画像をPNG形式で指定フォルダに保存
string file = string.Format(@"Pic_" + "ToImage-{0}.png", i);
image.Save(file, ImageFormat.Png);
}
}
 実行結果は正常で、SpirePDF_run_240917.pngPDFファイルのページ毎の画像ファイルが作成されました。SpirePDF_res_240917.png画像の一番上に、無償版にありがちな「警告」が出ていますが、この画像を文字起こしした後に削除することもできそうです。

 今回、PDFファイルを画像化することに目処が立ちました。次回はこの画像を使った「文字起こし」にトライしたいと思います。

生成AIでアプリ開発をする(4)

 前回のお話で、Claude3.5とGeminiから提案があった「iText」について調査しましたので、その備忘録です。iTextは、Javaおよび.NETでPDFファイルを作成および操作するためのライブラリでAGPL 3.0ライセンスです。

 調査に利用したプログラムは、前回にClaude3.5が生成してくれたものです。

 まず、Visual Studio上でiTextが利用できる環境をセットアップしました。「プロジェクト」から「NuGetパッケージの管理」を選択し、iText_setup1_240917.png検索窓に「iText」を記入して、「itext7」を選択しました。「インストール」ボタンを押すと、iText_setup2_240917.png変更のプレビューが表示されるので「適用」をクリックし、iText_setup3_240917.pngライセンスへの同意は「同意する」をクリックしました。iText_setup4_240917.png結果、正常にセットアップできました。ソリューションエクスプローラのパッケージ部分に登録されていることを確認できました。iText_setup5_240917.png
 プログラムの追加箇所は前回のプログラムの「ConvertPdfToImage関数」のみです。PDFファイルをページ毎に文字起こしするものです。
private void ConvertPdfToImage(string pdfPath, string outputPath)
{
// Placeholder for PDF to image conversion
// You'll need to implement this using a PDF rendering library

using (PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfPath)))
{
// PDFのページ分繰り返す
for (int page = 1; page <= pdfDoc.GetNumberOfPages(); page++)
{
// PDFから文字列を抽出
string s = PdfTextExtractor.GetTextFromPage(pdfDoc.GetPage(page));

// ページ番号 .txtのファイルを作成
using (var sw = new StreamWriter(@"Extract_" + page.ToString() + ".txt"))
{
// PDFの文字列を書き込む
sw.WriteLine(s);
}
}
}
}
 実行するにあたり、以下の3つのパターンを確認しました。

1. Webサイトをブラウザ(Chrome)からPDF印刷したもの
 プログラムを実行すると、本来の仕様であったPDFファイルの画像化がうまく行った意味の「PDF converted to image successfully」と表示されますが、今回追加したプログラムは画像化をせずに、直接iTextを使って文字起こしをしています。iText_run_240917.pngサンプルのPDFファイルは、OpenAIのWebサイトをブラウザでPDFファイルにしたもので、5ページありました。iText_pdf_240917.png結果、ページ毎に正しく文字起こしできていることを確認しました。iText_res_240917.png
2. 文章を含んだ画像をPDF印刷したもの
 このパターンもプログラム上では「PDF converted to image successfully」が出て正常終了したように見えましたが、文字起こしは失敗していました。何も文字を認識していない結果(白紙)でした。利用不可の結果です。

3. セキュリティ保護されたpdfファイル
 実行すると、プログラム上でエラーを吐いて失敗しました。iText_err_240917.png

 以上の結果から、最低でも2.の「文章を含んだ画像をPDF印刷したもの」の文字起こしまでは実施したいと思いました。そのためには、iText単独では実現が難しいので、当初の計画通り、一旦画像化した後に、文字認識をするフローを選択する必要があると思いました。PDFファイルの画像化については、次回お話しします。

Azureのリソースを削除する

 Azureのリソースが増えてきたので、使用しなくなったリソースを削除しようとすると、1_resource_delete_240916.png
以下のエラーが出ました。2_error_240916.png
いつもながら、書かれている内容が意味不明です・・。
「オブジェクトIDが「〇〇のクライアント△△」には「スコープ□□」で「アクション☆☆」を実行する認可がないか、スコープが無効です。」

 エラーの原因として、何かの権限がないことが予測されました。その場で内容が分かれば良いのですが、マイ○ソソフトのマニュアル関係は「非常に!」分かりにくいので、噛み砕いて解説してくれている個人ブログ等を見るのが近道ですね。調べて分かったことは、「Azureロールベースのアクセス制御(Azure RBAC)」が関係しているようだということでした。

 そういえば、以前、以下のような文面の英語のメールが来ていたことを思い出しました。

『2024年8月31日までに Azureのロールベースのアクセス制御 (RBAC)に移行します。この通知は、Azureクラシック管理者ロールを使用しているために受け取られています。
 2024年8月31日に、Azureクラシック管理者のロールは廃止されます。組織にアクティブな共同管理者ロールまたはサービス管理者ロールがある場合は、それまでにAzure RBACロールの使用に移行する必要があります。(すべてのAzureクラシックリソースと Azure Service Manager もその日に廃止されます。)』

 いつもの「上から目線」のマイ○ソソフトからのメールで「従わないとサービス利用できなくなるよ」という「脅迫じみたもの」でした(個人の意見です・・)。すでに8/31は過ぎているので、以前からAzureを使っていた「クラッシック管理者」の私にとっても、今まで通りのやり方ではダメだということなのでしょう。

 Azureロールベースのアクセス制御(Azure RBAC)については説明サイトに記載があるので、割愛しますが、要はAzureの各要素要素のサービスの管理について「ロール(権限)を事細かに設定できるよ」ということと理解しました。クラッシック管理者の時は、このような設定をしていませんでした。しなくても使えていたのが正解かも・・。ただ、今回運用が変わったので、設定しないと管理(リソースの削除等)ができないということなのでしょう。

 以下、リソースを削除するために実施した「アクセス制御(IAM)の設定手順」です。

 まず、該当のリソースグループの画面の「アクセス制御(IAM)」から「ロールの割り当ての追加」を選択しました。3_IAM_1_240916.pngIAM(Identity and Access Management)とは、「IDの管理・認証・認可」を指す言葉で、社内アプリケーションやクラウドサービスなど、企業が利用するシステムごとに設定された複数のIDを統合管理し、同時にアクセス権限の適切な管理を行うための仕組みのことですね。

 次に、ロール割り当て設定に移りますが、利用しているのが私一人なので「特権管理者ロール」を選択し、4_IAM_2_240916.png「所有者」を選択しました。5_IAM_3_240916.pngAzureの特権管理者ロールは、Azureリソースを管理したり他のユーザーにロールを割り当てたりする機能など持つロールです。

 次に「メンバーを選択する」をクリックし、該当するメンバー(今回は私一人ですが・・)を選択し、次へボタンを押しました。6_IAM_4_240916.pngメンバーとして登録されたことを確認して、次へ進みました。7_IAM_5_240916.png
 条件についても私一人なので「ユーザーにすべてのロールの割り当てを許可する(高い特権)」を選択し、最後に「レビューと割り当て」ボタンを押しました。8_IAM_6_240916.pngすぐにロールが割り当てられた旨が表示されました。9_IAM_8_240916.png 改めてリソースの削除を実行すると、10_resource_delete_2_240916.png今回はうまくいきました。11_resource_delete_OK_240916.png
 以上、今回の設定の流れは私にとって面倒なことでしたが、会社などで複数の人がAzureを利用する際に、事細かくロールが設定できるのは良いかもしれませんね。個人一人の利用の場合は基本的に特権権限なので、細かいロールはあまり考えなくても良いですが・・。

生成AIでアプリ開発をする(3)

 前回に続いて、今回はClaude3.5(無償版)、Gemini(無償版)で生成されたプログラムを見て行こうと思います。

2. Claude3.5
 前回のGPT4oと同じプロンプトを英語で与えました。以下、回答です。
Windowsフォームを使ってC#でPDF転記アプリケーションを作成したいとのことですね。このアプリケーションを実装するためのコード構造と主要コンポーネントの概要を説明します。ここでは、基本的な構造を説明します:

【プログラム】

このコードはシャレコ・アプリケーションの基本構造を提供する。以下にいくつかの重要なポイントを示します:

1. メインフォーム(Chareco)には、要件で説明した UI 要素が含まれています。
2. PDFファイルに対してドラッグ・アンド・ドロップ機能が実装されています。
3. PDFから画像への変換とOCRのためのプレースホルダーメソッドが含まれています。
4.OCRメソッドは、要求されたようにAzure OpenAIを使用しますが、プレースホルダを実際のAPI呼び出しに置き換える必要があります。
5. 認識されたテキストを保存するために、「結果保存」ボタン機能が実装されています。

アプリケーションを完成させるには、以下の作業が必要です:

1. 実際のPDFから画像への変換を `ConvertPdfToImage` メソッドで実装します。これにはiTextSharpのようなライブラリを使うとよいでしょう。
2. Azure OpenAIまたはOpenAIのGPT-4 Vision APIを使ってOCR実装を洗練させる。
3. Windows FormsデザイナーでUIをデザインする。
4. 潜在的なエラーやエッジケースを処理する。
5. 必要な追加機能を実装する。

Program.csファイルでは、最小限の実装を使用することができます:

```csharp
using System;
using System.Windows.Forms;

namespace Chareco
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Chareco());
}
}
}
```

PDF処理とAzure OpenAI統合に必要なNuGetパッケージを忘れずにインストールしてください。また、使用する特定のAPIとライブラリに基づいてコードを調整する必要があるかもしれません。

このコードのどの部分についても、説明したり詳しく説明したりする必要がありますか?
 上記のプログラムは以下の通りです(注:生成AIが出力したものでそのままでは動きません)。プログラム中に記載されているiTextとAzureについて、このプログラムの中で動かそうとしましたが、うまく行かなかったので、関係している部分を一旦コメントアウトして、基本動作のみの確認を行いました。iTextとAzureについては、別の機会にお話しします。
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas.Parser;
using iText.Kernel.Pdf.Canvas.Parser.Listener;
using System.Drawing.Imaging;
using Azure;
using Azure.AI.OpenAI;

namespace Chareco
{
public partial class Chareco : Form
{
private string pdfFilePath;
private string imageFilePath;

public Chareco()
{
InitializeComponent();
SetupDragDrop();
}

private void SetupDragDrop()
{
dropPanel.AllowDrop = true;
dropPanel.DragEnter += new DragEventHandler(DropPanel_DragEnter);
dropPanel.DragDrop += new DragEventHandler(DropPanel_DragDrop);
}

private void DropPanel_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy;
}

private void DropPanel_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files.Length > 0 && Path.GetExtension(files[0]).ToLower() == ".pdf")
{
pdfFilePath = files[0];
fileNameTextBox.Text = pdfFilePath;
}
}

private void ImageConversionButton_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(pdfFilePath))
{
MessageBox.Show("Please load a PDF file first.");
return;
}

try
{
// Convert PDF to image (this is a placeholder - you'll need to implement the actual conversion)
imageFilePath = Path.ChangeExtension(pdfFilePath, ".png");
ConvertPdfToImage(pdfFilePath, imageFilePath);
MessageBox.Show("PDF converted to image successfully.");
}
catch (Exception ex)
{
MessageBox.Show($"Error converting PDF to image: {ex.Message}");
}
}

private void ConvertPdfToImage(string pdfPath, string outputPath)
{
// Placeholder for PDF to image conversion
// You'll need to implement this using a PDF rendering library
}

private async void CharacterRecognitionButton_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(imageFilePath))
{
MessageBox.Show("Please convert the PDF to an image first.");
return;
}

try
{
string recognizedText = await PerformOCR(imageFilePath);
outputTextBox.Text = recognizedText;
}
catch (Exception ex)
{
MessageBox.Show($"Error performing character recognition: {ex.Message}");
}
}

private async Task<string> PerformOCR(string imagePath)
{
// Placeholder for OCR using Azure OpenAI
// You'll need to implement this using the Azure OpenAI SDK
// This is a simplified example and may need adjustment based on the actual API
var client = new OpenAIClient(
new Uri("Your Azure OpenAI Endpoint"),
new AzureKeyCredential("Your Azure OpenAI Key")
);

var imageBytes = File.ReadAllBytes(imagePath);
var base64Image = Convert.ToBase64String(imageBytes);

var chatCompletionsOptions = new ChatCompletionsOptions()
{
Messages =
{
new ChatMessage(ChatRole.System, "You are an OCR system. Describe the text in the image."),
new ChatMessage(ChatRole.User, $"[A base64 encoded image: {base64Image}]")
},
MaxTokens = 500
};

Response<ChatCompletions> response = await client.GetChatCompletionsAsync(
"Your-Deployment-Name",
chatCompletionsOptions
);

return response.Value.Choices[0].Message.Content;
}

private void ResultSaveButton_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(outputTextBox.Text))
{
MessageBox.Show("No text to save.");
return;
}

SaveFileDialog saveFileDialog = new SaveFileDialog
{
Filter = "Text files (*.txt)|*.txt",
DefaultExt = "txt",
AddExtension = true
};

if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
File.WriteAllText(saveFileDialog.FileName, outputTextBox.Text);
MessageBox.Show("Text saved successfully.");
}
}
}
}
GPT4oに比べClaude3.5の方が個人的にプログラムが洗練されているように思えました。

3. Gemini
 GPT4o、Claude3.5と同じプロンプトを英語で与えましたが、1回目の回答はプロンプトの内容を「鸚鵡返し」した形で、コードを出してくれませんでした。以下、回答です。
C#でPDFファイル転写アプリケーションを作成するお手伝いをさせていただければ幸いです。以下は、提供された回答の良い点を取り入れ、潜在的な問題に対処する包括的な概要です:

**1. アプリの目的

- PDF ファイルを読み取り、書かれたテキストをテキストとして出力する。
- ファイルの読み込み、書き起こし、結果の保存のためのユーザーフレンドリーなインターフェイスを提供する。

**2. UIの概要

- メインフォーム:***。
- ユーザーがPDFファイルをドラッグ&ドロップできるパネルです。
- ファイル名表示:** 読み込まれたPDFファイルの名前を表示するテキストボックス。
- 画像変換ボタン:** PDFを画像(PNGなど)に変換します。
- OpenAIまたはAzureOpenAIサービスを使用して、画像上でOCRを実行します。
- 転写出力ボックス:転写されたテキストを表示し、編集を可能にします。
- テキストファイルとして保存します。

**3. 具体的なアプリケーション操作の流れ

1. **ファイル読み込み:*** 1.
- PDFファイルを 「Drop File Area 」にドラッグ&ドロップします。
- ファイル名表示」テキストボックスにファイル名が表示されます。
2. **画像変換:**。
- 画像変換」ボタンをクリックします。
- PDFが画像(例:PNG)に変換され、一時的な場所に保存されます。
3. **文字認識:***。
- ユーザーは「文字認識」ボタンをクリックします。
- OCRエンジン(OpenAIまたはAzureOpenAI)が画像を処理し、転写されたテキストを返します。
- 転写されたテキストは、「転写出力ボックス 」に表示されます。
4. **編集と保存:***。
- ユーザーは、「Transcription Output Box 」で書き起こしたテキストを編集することができます。
- 結果保存」ボタンをクリックすると、編集したテキストがテキストファイルとして保存されます。

**4. ライブラリとツール

- Windows Forms: **ユーザーインターフェースの作成に使用します。
- OpenAIまたはAzureOpenAI:** 文字認識サービス用。
- PDF ライブラリ:** PDF ファイルの読み取りと変換に使用します (例: iTextSharp、Ghostscript)。
- 画像処理ライブラリ:** 画像変換処理用(例: System.Drawing)。

**5. 開発環境

- Visual Studio 2022:** C#開発用。
- .NET 6.0 または 8.0:** ターゲットフレームワーク。

**6. アプリケーションの出力形式

- ソリューション名:** Chareco
- フォーム名:** Chareco
- 出力ファイル:** Chareco.cs、Chareco.Designer.cs、Program.cs (最小)

追加考慮事項:*** エラー処理:*** 適切なエラー処理メカニズムを実装する。

- エラー処理:** 例外をキャッチし、ユーザーに有益なメッセージを提供するために、適切なエラー処理メカニズムを実装する。
- 進捗表示:** 画像変換中および文字認識中に進捗インジケータを表示し、ユーザーにフィードバックを提供することを考慮する。
- 設定:*** OCRエンジン、出力ファイル形式、言語などの設定をユーザーがカスタマイズできるようにします。
- パフォーマンスの最適化: **大きなPDFファイルや高解像度の画像を扱う場合は、パフォーマンスのためにコードを最適化する。
- アクセシビリティ:*** アクセシビリティ・ガイドラインに従うことで、障害を持つユーザーがアプリケーションにアクセスできるようにします。

これらのガイドラインに従って、提供された回答からのフィードバックを取り入れることにより、C#で堅牢でユーザーフリーなPDFファイル転記アプリケーションを作成できます。
 2回目の質問は、端的に「実際のコードを書いてください。」とだけ言いました。回答内容は以下の通りです。
C#でPDFファイル転写アプリケーションを作成する

1. プロジェクトの作成とUIの設計
Visual Studioで新しいWindows Formsアプリケーションを作成し、「Chareco」と名付けます。フォームに以下のコントロールを配置します。

Drag and Drop Panel: Panelコントロールを配置し、AllowDropプロパティをtrueにします。
File Name Textbox: TextBoxコントロールを配置し、読み込んだPDFファイルのパスを表示します。
Image Conversion Button: Buttonコントロールを配置し、クリック時にPDFを画像に変換します。
Character Recognition Button: Buttonコントロールを配置し、クリック時に画像から文字認識を行います。
Transcription Output Textbox: TextBoxコントロールを配置し、転写結果を表示します。
Result Save Button: Buttonコントロールを配置し、クリック時に転写結果をテキストファイルに保存します。

2. コードの実装

【プログラム】

コードは注意してご使用ください。

3. 説明
iTextSharp: PDF操作に便利なライブラリです。NuGetでインストールできます。
OpenAI API: 画像からテキストを抽出するAPIです。事前にAPIキーを取得し、コードに設定してください。
Drag and Drop: DragEnterイベントとDragDropイベントを使用して、PDFファイルをドラッグアンドドロップで読み込みます。
画像変換: iTextSharpなどを使用してPDFを画像に変換します。
文字認識: OpenAI APIのImage.CreateCompletionAsyncメソッドを使用して、画像からテキストを抽出します。
転写結果の保存: System.IO名前空間のクラスを使用して、テキストファイルを保存します。
注意点
OpenAI APIの利用: OpenAI APIの利用には料金がかかる場合があります。
画像変換の品質: 画像の品質によって文字認識の精度が大きく変わる場合があります。
エラー処理: 例外処理などを適切に行い、プログラムが異常終了しないようにします。
UIの改善: より洗練されたUIにするために、様々なコントロールやレイアウトを検討してください。
このコードはあくまで基本的な実装であり、実際の開発では、より詳細なエラー処理やパフォーマンスチューニングが必要になります。

その他
OCRエンジンの選択: OpenAI以外にも、Google Cloud Vision APIなど、様々なOCRエンジンがあります。
PDFライブラリの選択: iTextSharp以外にも、PDFBoxなど、様々なPDFライブラリがあります。
UIデザイン: より使いやすく、視覚的に美しいUIにするために、デザインツールを活用することも検討してください。
参考資料
iTextSharp: [無効な URL を削除しました]
OpenAI API: [無効な URL を削除しました]
このコードをベースに、ご自身の要件に合わせてカスタマイズしてください。
**
※ このコードを実行する前に、必要なライブラリをNuGetでインストールし、OpenAIのAPIキーを設定してください。**

より詳細な説明やカスタマイズについては、お気軽にご質問ください。

補足:

画像変換: iTextSharpを用いたPDFから画像への変換の具体的なコードは、PDFの構造や必要な画像形式によって異なります。
エラー処理: 例外処理や、ファイルが存在しない場合の処理などを適切に行う必要があります。
UIデザイン: Windows Forms Designerを用いて、より直感的に操作できるUIを設計することができます。
パフォーマンス: 大量のPDFファイルを処理する場合、パフォーマンスチューニングが必要になる場合があります。
ご希望に応じて、これらの点についても詳しく説明できます。

また、具体的なエラーが発生した場合には、エラーメッセージやコードの該当部分を提示いただけると、より的確なアドバイスができます。
出力されたプログラムは以下の通りです。
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using iText.Kernel.Pdf; // PDF操作用ライブラリ
using OpenAI.API; // OpenAI API用ライブラリ

namespace Chareco
{
public partial class Chareco : Form
{
private string pdfFilePath;
private string imageFilePath;

public Chareco()
{
InitializeComponent();
dragAndDropPanel.AllowDrop = true;
dragAndDropPanel.DragEnter += DragEnter;
dragAndDropPanel.DragDrop += DragDrop;
}

private void DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
}

private void DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files.Length > 0 && Path.GetExtension(files[0]).Equals(".pdf", StringComparison.OrdinalIgnoreCase))
{
pdfFilePath = files[0];
fileNameTextBox.Text = pdfFilePath;
}
}

private async void imageConversionButton_Click(object sender, EventArgs e)
{
// PDFを画像に変換する処理(iTextSharpなどを使用)
// ...
imageFilePath = "converted.png"; // 仮のファイルパス
}

private async void characterRecognitionButton_Click(object sender, EventArgs e)
{
var openai = new OpenAIAPI("YOUR_API_KEY");
var response = await openai.Image.CreateCompletionAsync(new ImageRequest
{
Model = "gpt-4-vision",
Image = new ImageFile(imageFilePath),
Prompt = "What is written in this image?"
});

transcriptionOutputTextBox.Text = response.Choices[0].Text.Value;
}

private void resultSaveButton_Click(object sender, EventArgs e)
{
// 転写結果をテキストファイルに保存する処理
// ...
}
}
}
GPT4o、Claude3.5に比べ、中身が非常に薄い内容と個人的には思いました。

 最後に、今回利用した3種類の生成AI(GPT4o、Claude3、Gemini)によるアプリ開発のまとめをします。AppGeneration_summary1_240916.jpg Visual Studioを起動させ、プロジェクトとフォームを作成する作業は自動化ができないと思いますので、自分で行いました。また、UI設計において、今回は各オブジェクトをフォーム上に個別に作成する必要がありましたが、スケッチ(配置図)があるので、適切なプロンプトを入力することで改善できるのではないかと思いました。AppGeneration_summary2_240916.jpg 出力された「Chareco.cs」は、具体的な処理の中身は生成されておらず、メッセージボックスが表示されるだけのものでした。一度に全ての機能をプロンプトに盛り込むのは難しいので、機能を小分けにしてプロンプトを送り、個々に完成させていくスタイルが望ましいのではないかと思い直しました。
 今回、自分の考えていなかったアイデア(ライブラリ)も提案してもらっているので、今後、確認して行こうと思います。

生成AIでアプリ開発をする(2)

 前回の続きです。今回は「PDFファイルの文字起こしアプリ」を作成しようと思います。

 まず初めに、アプリの完成イメージを作成しました。このGUI部分は、生成AIに頼むことができない部分と何となく思ったので、今回は自分でスケッチしました。Form_image_240909.png PDFファイルをドラッグアンドドロップしてアプリに読み込んで、そのPDFファイルを一旦、画像化します。その画像について文字認識する仕様にしました。文字認識した結果はテキストボックスに出力され、ファイル保存できるようにしました。

 今回利用する生成AIはGPT4o(無償版)、Claude3.5(無償版)、Gemini(無償版)の3種類です。前回GPTがアドバイスしてくれたプロンプトの書き方を元に、3種類の生成AIに同じ内容で投げかけて比較をしました。また、すべてのプロンプトは英語に翻訳して投げかけました。以下、分かりやすくするために日本語で表記します。
あなたは優秀なソフトウェアエンジニアです。

C#で「PDFファイルの文字起こしアプリ」を作成したいです。

1. アプリの目的
「PDFファイルを読み込んで、書かれている文章をテキスト化して出力するアプリを作成したい。」

2. UIの概要
「添付画像を参考に、メインフォームには以下のUI要素を作成して配置したい:
- PDFファイルをドラッグアンドドロップして、読み込むためのパネル(添付図の"Drop a file here.")
- 読み込んだPDFファイルのファイル名を表示するテキストボックス
- 読み込んだPDFファイルを画像変換するための「Image Conversion」ボタン
- 画像から文字起こしを実行させるための「Character Recognition 」ボタン
- PDFファイルの文字起こし結果を表示・編集するテキストボックス
- 文字起こしされた内容表示のテキストボックスの文字を出力するための「Result Save」ボタン」

3. 具体的なアプリの操作の流れ
1. Windowsフォームアプリケーションの「ファイルをドロップしてください」にPDFファイルをドラッグ&ドロップする。PDFファイルをドラッグ&ドロップし、正常に完了するとテキストボックスにPDFファイル名が表示される。
    (例)C:¥Users¥Documents¥sample.pdf
2. 目的のPDFファイルがテキストボックスにロードされた後、「画像変換」ボタンをクリックすると、プログラムはpdfファイルを画像ファイル(pngファイル)に変換し、pngファイルは自動的にPCに保存されます。
3. 「文字認識」ボタンを押すと、画像情報から文字を認識し、文字認識を行う。文字認識のエンジンは、OpenAIやAzureOpenAIのサービスを利用することができる。 文字認識結果は「出力」テキストボックスに表示される。このテキストボックスは編集可能で、テキストの誤りを修正することもできる。
4. 「結果保存」ボタンを押すと、編集したテキスト文書をPCに保存できる。

4. 使用したいライブラリやツール
「標準のWindows Formsを使用したい。」
「OpenAIやAzureOpenAIのGPT4あるいはGPT4oを利用してもよい。」
「PDFファイルからの文字起こしの方法特に指定はないが、OCR形式を検討してほしい。OCRのライブラリは特に指定はない。」

5. 開発環境
「Visual Studio 2022を使用。ターゲットフレームワークは.NET 6.0か8.0。」

6. アプリの出力形態
「ソリューション名はCharecoとしたい。」
「フォーム名もCharecoとし、Chareco.csとChareco.Designer.csの双方を出力して欲しい。」
「Program.csは必要最小限の出力とし、フォームに関する詳細設定は含まないようにしたい。」

 プロンプトに対する返信を生成AI毎に見て行こうと思います。まずはGPT4oです。

1. GPT4o
 以下、回答です。
「チャレコ」アプリケーションを要件に基づいて作成するには、以下のようなアプローチで開発します:

### 1. **プロジェクトとメインフォームを作成する。
- プロジェクトとメインフォームを作成します。
- メインフォーム名:** `Chareco`
- フレームワーク:** .NET 6.0 または 8.0

### 2. UIの設計:** Designing the UI:**
- **ドラッグアンドドロップパネル:**
- Panel`コントロールを使用し、ファイルのドラッグ&ドロップを許可するプロパティを設定します。
- DragEnter`と`DragDrop`イベントを処理してPDFファイルを検出します。
- **PDFファイル名のテキストボックス:**
- パネルの下に、ファイルパスを表示するための`TextBox`を追加します。
- 読み取り専用に設定してください。
- **ボタン:**
- 画像変換ボタン:** PDFから画像への変換をトリガーします。
- 文字認識ボタン:** OpenAIまたはAzureOpenAIを使用してOCRを開始します。
- **結果保存ボタン:** 転写・編集されたテキストを保存します。
- **出力テキストボックス:**
- OCR結果を表示・編集するための複数行のテキストボックスです。

### 3. **機能の実装:####

#### a. **ドラッグ・アンド・ドロップ PDF 読み込み:** ドラッグ・アンド・ドロップで PDF を読み込みます。
- DragEnter` イベントを処理して、ドラッグされているファイルが PDF であることを確認します。
- DragDrop` イベントで、ファイルパスを抽出し、テキストボックスに表示します。

#### b. **画像変換:***。
- PDFのページを画像に変換するには、`PdfSharp`や`iTextSharp`のようなライブラリを使用します。
- 画像はPC上のあらかじめ定義された場所に保存する。

#### c. **文字認識(OCR):***。
- AzureOpenAI または他の OCR サービスを使用して画像を処理します。
- 認識されたテキストを出力テキストボックスに表示します。

#### d. **結果を保存する:**。
- 保存機能を実装して、書き写したテキストをファイルに保存します。

### 4. **コード例
以下は、フォームとその機能の簡単なコード概要です:

### 5. **最終化とビルド:*** プロジェクトをビルドし、各機能が期待通りに動作することをテストする。
- プロジェクトをビルドし、各機能が期待通りに動作するかテストします。
- 提供されたデザインに合わせて、必要に応じて UI をカスタマイズします。

このセットアップにより、「チャレコ」アプリケーションの良い出発点が得られるはずです。さらなる詳細や調整が必要な場合はお知らせください!
4.のコード例のプログラムは以下の通りです。
using System;
using System.Windows.Forms;
using System.Drawing; // For image handling
using System.IO; // For file handling

namespace Chareco
{
public partial class Chareco : Form
{
public Chareco()
{
InitializeComponent();
}

private void panel_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (Path.GetExtension(files[0]).ToLower() == ".pdf")
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}
}

private void panel_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
textBoxFileName.Text = files[0];
}

private void btnImageConversion_Click(object sender, EventArgs e)
{
// Convert PDF to Image
string pdfPath = textBoxFileName.Text;
string imagePath = ConvertPdfToImage(pdfPath);
MessageBox.Show($"PDF converted to image: {imagePath}");
}

private string ConvertPdfToImage(string pdfPath)
{
// Logic to convert PDF to image
// Use libraries like PdfSharp or iTextSharp
// Save image and return the path
return "imagePath.png"; // Placeholder
}

private void btnCharacterRecognition_Click(object sender, EventArgs e)
{
string imagePath = "imagePath.png"; // Placeholder
string recognizedText = RecognizeTextFromImage(imagePath);
textBoxOutput.Text = recognizedText;
}

private string RecognizeTextFromImage(string imagePath)
{
// Use OpenAI or AzureOpenAI OCR services
return "Recognized text"; // Placeholder
}

private void btnSaveResult_Click(object sender, EventArgs e)
{
string textToSave = textBoxOutput.Text;
SaveTextToFile(textToSave);
MessageBox.Show("Text saved successfully.");
}

private void SaveTextToFile(string text)
{
// Save text to a file
}
}
}
プログラム自身にバグはありませんでしたが、足りない部分が多い結果でした。プロンプトが適切でないのも理由ですが・・。以下、詳しく見ていきましょう。

 まず、1.に記載の「プロジェクトとメインフォームを作成する」は、当然と言えば当然ですが、自分で行いました。Visual Studioを起動させて、プロジェクトを作成しました。Project_240909.pngまた、2. UIの設計についても、Formがまっさらな状態なので(Chareco.Designer.csの出力がないので)、各オブジェクトを作成しました。Form_240909.pngつまり、出力されていたプログラムは「Chareco.cs」だけで「Program.cs」と「Chareco.Designer.cs」は生成されていなかったので、不足部分を補って動く形にしました。また、今回の結果は、具体的な処理の中身の記載はなく、メッセージボックスが表示されるだけのものだったので、そのままの形で動作確認しました。

 PDFファイルをドラッグ&ドロップすると、テキストボックスにファイル名が表示される所は指示通りでした。App_run1_240909.pngImage Conversionボタンを押すと、ConvertPdfToImage関数を実行しますが、中身はなく、メッセージが表示されるだけでした。App_run2_240909.pngCharacter Recognitionボタンを押すと、RecognizeTextFromImage関数を実行しますが、これも中身はなく、テキストボックスにメッセージが表示されるだけでした。App_run3_240909.pngResult Saveボタンを押すと、SaveTextToFile関数を実行しますが、これも空っぽなので、メッセージだけです。App_run4_240909.png 動きとしてはバグもなく正しいのですが、処理の中身が欠如しており、残念な結果でした。中身は個別に指示をするなどして作り込んで行くしかなさそうです。加えて、Form画面にオブジェクトを手作業で作り込んでいくのも面倒ですね。このあたりも、プロンプトを駆使すれば解決しそうな気がしますが・・。

 今回はFirst Trialで、不十分な所も多かったですが、プロンプトを改善すれば、それなりに使って行けそうな気がしました。次回はClaude3.5で試した結果をお話しします。

ご訪問者数

(Since 24 July, 2016)

タグクラウド


プロフィール

Dr.BobT

Author: Dr.BobT
興味のおもむくままに生涯考え続けるエンジニアでありたい。

月別アーカイブ

メールフォーム

名前:
メール:
件名:
本文: