肝は、Excel + ClosedXML の組み合わせが、LibreOffice Calc + UNO API でできるか?ということです。私としては C# から UNO API を使えればよいのですが、前記事から C# から UNO API を使うには難点があるので、素直に Python でコーディングをします。
まあ、コーディングをするといってもベースを作るのは AI エージェントなので、それを動かしてちょこちょこと Python コードを修正することになります。
LibreOffice の UNO API は戻り値が Any なのでコード補完が効きません。多分、戻り値が object なので、そういう仕様になっているのでしょうが、コードを書くときに面倒です。というか、どのメソッドを呼び出せるのか実行時にしかわかりません。 ひょっとしてメソッド名を全部覚えているのか? とも思わなくもないのですが、もっと敷居を下げておきたいです。自分のためにも。
おそらく、C++、Java で UNO API を使うためには大量のインターフェースを定義してあるはずです。
import uno
import re
from typing import Any, TYPE_CHECKING, cast
# typings/calc.pyi は型チェック専用に読み込む(実行時はフォールバック)
if TYPE_CHECKING:
from calc import XDesktop, XComponent, XSpreadsheet, XCellRange, Rectangle # type: ignore
else:
XDesktop = XComponent = XSpreadsheet = XCellRange = Rectangle = Any
"""
棒グラフを作成するスクリプト
"""
def connect_to_libreoffice() -> tuple[XDesktop, XComponent, XSpreadsheet]:
"""LibreOffice に接続してアクティブな Calc シートを取得する"""
local_ctx = uno.getComponentContext()
resolver = local_ctx.ServiceManager.createInstanceWithContext(
"com.sun.star.bridge.UnoUrlResolver", local_ctx)
ctx = resolver.resolve(
"uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext")
smgr = ctx.ServiceManager
desktop : XDesktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
model = desktop.getCurrentComponent()
if model is None:
raise RuntimeError("LibreOffice Calc ドキュメントが開かれていません")
sheet = model.getCurrentController().getActiveSheet()
return desktop, model, sheet
if TYPE_CHECKING のところで、コーディング時と実行時の切り替えをしなといけないのが面倒なのですが、これで Any となっている型を無理矢理 XCellRange などの型に変えることができます。 本来は、cast 関数を使って正式にキャストをすればいいのですが、どうせ Any からのキャストでしかないので、変数の型だけ決めて、そこに無理やり押しこみます。
UNO API のクラスは膨大にあって、ちまちま手作業で変換するのは大変です。いや、そもそも使いたいのは Calc 関係だけなので、全部を変換するのは大袈裟だし、手間がかかります。 基本は AI エージェントにコードを作って貰うつもりなので、すべての型が必要なわけではありません。ちょっと手直しをするとか、数行のマクロコードを書くときにコード補完ができればよいのです。
C++ や Java のライブラリを利用すればいいのでは?
UNO API のインターフェースは、Python だけではありません。C++ や Java からも使うことができます。ドキュメントは非常に少ないですが、C# から、つまり .NET からのアクセスも可能となっています。
結論から言うと現状の 25.8.3 では動作しません。これは .NET 関係で作られているラッパーがおかしいのか、プログラムがおかしいのか不明ですが、UNO API サーバーに繋がりません。
using System;
using System.Diagnostics;
using uno;
using uno.util;
using unoidl.com.sun.star.bridge;
using unoidl.com.sun.star.container;
using unoidl.com.sun.star.frame;
using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.sheet;
using unoidl.com.sun.star.table;
using unoidl.com.sun.star.text;
using unoidl.com.sun.star.uno;
using unoidl.com.sun.star.beans;
class Program
{
static void Main()
{
try
{
Console.WriteLine("Starting...");
string[] programPathCandidates =
{
@"C:\\Program Files\\LibreOffice\\program",
@"C:\\Program Files (x86)\\LibreOffice\\program"
};
string? libreOfficeProgramPath = null;
foreach (var p in programPathCandidates)
{
if (System.IO.Directory.Exists(p))
{
libreOfficeProgramPath = p;
break;
}
}
if (libreOfficeProgramPath == null)
{
Console.WriteLine("LibreOffice program folder not found. Please adjust path.");
foreach (var p in programPathCandidates)
{
Console.WriteLine($"Tried: {p}");
}
return;
}
Console.WriteLine($"UNO_PATH target: {libreOfficeProgramPath}");
// Ensure UNO can locate LibreOffice binaries.
Environment.SetEnvironmentVariable("UNO_PATH", libreOfficeProgramPath, EnvironmentVariableTarget.Process);
var bootstrapIni = System.IO.Path.Combine(libreOfficeProgramPath, "fundamental.ini");
Environment.SetEnvironmentVariable("URE_BOOTSTRAP", $"vnd.sun.star.pathname:{bootstrapIni}", EnvironmentVariableTarget.Process);
string ureBin = System.IO.Path.Combine(libreOfficeProgramPath, "..\\URE\\bin");
string newPathSegment = libreOfficeProgramPath + ";" + ureBin;
string? currentPath = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
if (!currentPath.Contains(libreOfficeProgramPath, StringComparison.OrdinalIgnoreCase) || !currentPath.Contains(ureBin, StringComparison.OrdinalIgnoreCase))
{
Environment.SetEnvironmentVariable("PATH", newPathSegment + ";" + currentPath, EnvironmentVariableTarget.Process);
}
Environment.SetEnvironmentVariable("UNO_SERVICES", System.IO.Path.Combine(libreOfficeProgramPath, "uno_services.rdb"), EnvironmentVariableTarget.Process);
// Some environments need the working directory inside the LibreOffice program folder.
Environment.CurrentDirectory = libreOfficeProgramPath;
// Start (or reuse) LibreOffice headless with a socket connector.
var sofficePath = System.IO.Path.Combine(libreOfficeProgramPath, "soffice.exe");
var acceptArg = "--accept=socket,host=localhost,port=2002;urp;StarOffice.ServiceManager";
if (!System.IO.File.Exists(sofficePath))
{
Console.WriteLine($"soffice.exe not found at {sofficePath}");
return;
}
bool sofficeRunning = Process.GetProcessesByName("soffice.bin").Length > 0;
if (!sofficeRunning)
{
Console.WriteLine("Starting soffice headless...");
var psi = new ProcessStartInfo
{
FileName = sofficePath,
Arguments = $"--headless --nologo --norestore --nodefault {acceptArg}",
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = libreOfficeProgramPath
};
Process.Start(psi);
// Give soffice time to open the socket.
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
}
Console.WriteLine("Creating initial UNO context...");
XComponentContext localContext = Bootstrap.defaultBootstrap_InitialComponentContext();
XMultiComponentFactory localSmgr = localContext.getServiceManager();
XUnoUrlResolver resolver = (XUnoUrlResolver)localSmgr.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext);
Console.WriteLine("Resolving remote context via socket...");
XComponentContext remoteContext = (XComponentContext)resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext");
XMultiComponentFactory serviceManager = remoteContext.getServiceManager();
XDesktop? desktop = serviceManager.createInstanceWithContext("com.sun.star.frame.Desktop", remoteContext) as XDesktop;
if (desktop == null)
{
Console.WriteLine("Failed to acquire LibreOffice desktop.");
return;
}
Console.WriteLine("Desktop acquired");
XComponentLoader? loader = desktop as XComponentLoader;
if (loader == null)
{
Console.WriteLine("Failed to acquire component loader.");
return;
}
Console.WriteLine("Component loader OK");
// Reuse the active Calc document, or create a new one if none is open.
Console.WriteLine("Checking current component...");
XSpreadsheetDocument? calcDocument = desktop.getCurrentComponent() as XSpreadsheetDocument;
if (calcDocument == null)
{
Console.WriteLine("No active Calc. Creating new...");
var args = Array.Empty<PropertyValue>();
var component = loader.loadComponentFromURL("private:factory/scalc", "_blank", 0, args);
calcDocument = component as XSpreadsheetDocument;
}
if (calcDocument == null)
{
Console.WriteLine("No Calc document available.");
return;
}
Console.WriteLine("Calc document ready");
// Access the first sheet and write text to cell A1.
XSpreadsheets sheets = calcDocument.getSheets();
XIndexAccess sheetAccess = (XIndexAccess)sheets;
Any sheetAny = (Any)sheetAccess.getByIndex(0);
XSpreadsheet? sheet = sheetAny.Value as XSpreadsheet;
if (sheet == null)
{
Console.WriteLine("Failed to access the first sheet.");
return;
}
Console.WriteLine("Sheet acquired");
XCell cell = sheet.getCellByPosition(0, 0);
cell.setFormula("Hello by C#");
Console.WriteLine("Value written to A1.");
}
catch (System.Exception ex)
{
Console.WriteLine($"Failed to write to Calc: {ex.Message}");
Console.WriteLine(ex);
}
}
}
結果
PS H:\LibreOffice\net-ref> dotnet run
Starting...
UNO_PATH target: C:\\Program Files\\LibreOffice\\program
Creating initial UNO context...
Failed to write to Calc: External component has thrown an exception.
System.Runtime.InteropServices.SEHException (0x80004005): External component has thrown an exception.
at cppu.defaultBootstrap_InitialComponentContext(Reference<com::sun::star::uno::XComponentContext>*)
at uno.util.Bootstrap.defaultBootstrap_InitialComponentContext(String ini_file, IDictionaryEnumerator bootstrap_parameters)
at uno.util.Bootstrap.defaultBootstrap_InitialComponentContext()
at Program.Main() in H:\LibreOffice\net-ref\Program.cs:line 97
PS H:\LibreOffice\net-ref>
Sub test()
Dim sh As Worksheet
Set sh = ActiveSheet
sh.Cells(1, 1).Value = "増田智明"
End Sub
エラーになるので ChatGPT でコンバートすると以下のようになります。
Sub test
Dim oDoc As Object
Dim oSheet As Object
Dim oCell As Object
' 現在のドキュメントを取得
oDoc = ThisComponent
' アクティブシートを取得
oSheet = oDoc.CurrentController.ActiveSheet
' A1セルを取得 (行・列は0始まり)
oCell = oSheet.getCellByPosition(0, 0)
' 値を設定
oCell.String = "増田智明"
End Sub
Lib を参照している Python のバージョン部分「python-core-3.11.13」を開発環境で揃えないといけませんが、これで import uno のコード補完が有効になります。 ただし、もともと Any 型になっているものが多いのか、あまり役に立たないのですが、メソッド名とかクラス名からライブラリのコードにジャンプできるのはいいかもしれません。
varctor<int> vec = {1,2,3};
std::vector<int>::iterator it = vec.begin();
いや、これはもう無理です。まあ、実際こう書いていたわけですが、ちょっとインテリセンスとか何かの IDE の助けがないと難しいです。イテレータぐらいだから大丈夫だけど、C# のややこしい LINQ のポインタだとか諸々を加えると無理です。なので、型推論させて、C++ のほうは auto とか、C# の場合は var で受けるのが常です。
varctor<int> vec = {1,2,3};
auto it = vec.begin();
と型推論をさせます。ハッキリ言って、C/C++ でも後置型に変えてしまって、
auto x = 10 : int ;
auto y = 20 : int ;
だろうとは思います。実際のところ auto が使えるので、下記のようにキャストを使って無理矢理推論させることが可能です。あまり意味がありませんが。
auto x = (int)10 ;
auto y = (int)20 ;
C言語以前は、前置型であったのか?
C 言語以前が、前置型だったのか、とは思っていたのですが、これが違います。Ada が後置型で、なんと COBOL が後置型です。
これ、実際にコンパイラを作ろうとすると型の指定が前のほうにあったほうが楽で(これも人に依るとは思うのですが)、後にあるときはちょっと面倒なんですよね。あと、変数の前に var とか let とかあるのが無意味では?と思ったものです。まあ、Basic なんて Dim で定義したりするので、型はないのですが「変数であること」宣言みたいなものが var/let/Dim には存在します。
# コンパイル時の使用メモリ量をチェックする
プログラム言語には、型指定を前置と後置するものがある。
例えば、C言語やJavaは前置型指定であり、変数宣言時に型を指定する。
```c
int x = 10;
```
一方、Pascal や Kotlin などは後置型指定となる。
```pascal
var x: Integer;
x := 10;
```
プログラム言語の歴史上、前置型指定が主流であったが、後置型指定も徐々に普及してきている。
前置型指定は、コンパイルするときに型の指定が前置のほうがメモリ使用量が少なかったのではないか?と仮説を立てる。
これを検証する。
C 言語以前には、Ada が後置型であるので、Pascal 以前にも後置型があってもおかしくはない
```Ada
X : Integer := 10;
```
## 登場時期
- Fortran 1957 年 前置型
- COBOL 1959 年 後置型
- Pascal 1970 年代 後置型
- C 言語 1972 年 前置型
- Ada 1980 年代 後置型
- Java 1995 年 前置型
## 仮説
a. 前置型指定のほうがコンパイル時のメモリ使用量が少ない。このため、前置型が主流であり、メモリが増えるときに後置型が発生した
b. 前置型指定は慣習であり、メモリ使用量には影響しない。C 言語前後で前置型が作られたのは偶然である
a/b を検証する。
## 検証方法
1. 前置型指定言語と後置型指定言語で、同じアルゴリズムを実装する
2. コンパイル時のメモリ使用量を測定する
比較対象は、
- 構文木のメモリ使用量
- シンボルテーブルのメモリ使用量
利用メモリは、32KB 以下とする。
## 実装
- 字句解析の実装
- 構文解析の実装
- シンボルテーブルのメモリ容量を比較
- 構文解析時の構文木のメモリ容量を比較
## 文法
以下の文法を解析できるコードを作成する。
### C 言語(前置型指定)
```C
type_specifier: "int" | "float" | "char" ;
declaration: type_specifier identifier ";" ;
```
```C
int x, y;
int sum ;
x = 10 ;
y = 20 ;
sum = x + y ;
int func(int a, int b) {
return a + b ;
}
```
### Pascal(後置型指定)
```pascal
type_specifier: "Integer" | "Real" | "Char" ;
declaration: "var" identifier ":" type_specifier ";" ;
```
```pascal
var x, y: Integer;
var sum: Integer;
x := 10 ;
y := 20 ;
sum := x + y ;
function func(a: Integer; b: Integer): Integer;
begin
return a + b ;
end;
```
### Fortran(前置型指定)
```fortran
type_specifier: "INTEGER" | "REAL" | "CHARACTER" ;
declaration: type_specifier identifier_list ";" ;
```
```fortran
INTEGER x, y
INTEGER sum
x = 10
y = 20
sum = x + y
INTEGER FUNCTION func(a, b)
INTEGER a, b
func = a + b
END FUNCTION func
```
### COBOL(後置型指定)
```cobol
type_specifier: "PIC 9" | "PIC X" ;
declaration: "01" identifier "PIC" type_specifier "." ;
```
```cobol
IDENTIFICATION DIVISION.
PROGRAM-ID. SAMPLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 X PIC 9(4) VALUE 0.
01 Y PIC 9(4) VALUE 0.
01 SUM PIC 9(4) VALUE 0.
01 A PIC 9(4) VALUE 0.
01 B PIC 9(4) VALUE 0.
01 RESULT PIC 9(4) VALUE 0.
PROCEDURE DIVISION.
MAIN-PARA.
MOVE 10 TO X.
MOVE 20 TO Y.
ADD X TO Y GIVING SUM.
MOVE 5 TO A.
MOVE 7 TO B.
PERFORM FUNC-PARA.
DISPLAY "SUM = " SUM.
DISPLAY "FUNC(A,B) = " RESULT.
STOP RUN.
FUNC-PARA.
ADD A TO B GIVING RESULT.
EXIT.
```
### Ada(後置型指定)
```ada
type_specifier: "Integer" | "Float" | "Character" ;
declaration: identifier ":" type_specifier ";" ;
```
```ada
X : Integer;
Y : Integer;
SUM : Integer;
X := 10;
Y := 20;
SUM := X + Y;
function Func(A : Integer; B : Integer) return Integer is
begin
return A + B;
end Func;
```
### Java (前置型指定)
```java
type_specifier: "int" | "float" | "char" ;
declaration: type_specifier identifier ";" ;
```
```java
int x, y;
int sum;
x = 10;
y = 20;
sum = x + y;
int func(int a, int b) {
return a + b;
}
```
### Kotlin(後置型指定)
```kotlin
type_specifier: "Int" | "Float" | "Char" ;
declaration: "var" identifier ":" type_specifier ;
```
```kotlin
var x: Int
var y: Int
var sum: Int
x = 10
y = 20
sum = x + y
fun func(a: Int, b: Int): Int {
return a + b
}
```
### Lisp(前置型指定)
```lisp
type_specifier: 'int | 'float | 'char ;
declaration: (declaim (type type_specifier identifier)) ;
```
```lisp
(declaim (type 'int x))
(declaim (type 'int y))
(declaim (type 'int sum))
(setf x 10)
(setf y 20)
(setf sum (+ x y))
(defun func (a b)
(+ a b))
```
Plan モードで作成
vscode + copilot に Plan モードがやって来たので、先の agent.md(つまりは要件定義)から具体的な実装計画を立てて貰います。
これが凄いのは、複数のロボットアームが協調動作をしているのですが、相互にぶつからないように作業ができています。これ、自動車の塗装や溶接だと事前プログラムで衝突を回避するのですが、不二越ロボットはリアルタイム(MZS OS だったか)で相互干渉を避けることができます。これは木製の線路かたずけを行っているデモですが、他にもボールやビスの片づけとか4,5台のロボットアームが協調動作するところが見られます。
実際にはロボットアーム自体の物理量(振り回しやトルクとか)があるので、その許容範囲を計算するのと、作業台の上の道具の位置が若干ことなるとか、そういう差異を吸収しないといけません。そのあたりは、具体的にロボットアームの動作プログラムをしている方がぽろぽろと X のタイムラインにもいるので探してください。企業秘密なのでなかなか見つかりませんが、趣味ロボットを作っている方を漁ると出会えたりします。つまりは、仕事でもロボットだし、趣味でもロボットなわけです。
ちなみに写真を取り忘れてしまったのですが、ロボットアームの作業を「共創」するパターンがあります。先の MZS OS の場合は一社だけの協調作業ですが、「共創」する場合は、多様な会社のロボットアームを協調動作させることが目的になります。いろいろな会社が、その会社特有のアームを持ち出して、協調的に作業をできるというスタイルです。今回でも、このスタイルを主張する会社が2社ほどありました。
他社のロボットアームが AI などを使った自動化に重きを置くのですが、人力の操作に重きを置いたのが「人機一体」です。いわゆる従来型の人が操作するロボットアームになりますが、重機や臨機応変な作業にはこれが有効になります。
ChatGPTが一般的に使われ始めて2年ほど経ち、今度は AI エージェントというのが今年の4月頃から流行りはじめました。ChatGPT を使って、プログラミングのコードを教えて教えて貰うのはいいのですが、いちいちコードをコピペしないとイケなかったり、そもそも大量のコードをを ChatGPT に貼り付けないと文脈をうまく読み取ってくれないという問題がありました。が、これを Visual Studio Code 上なので直接ファイルを弄ってくれるのが AI エージェントの役割です。ちょっと危険な気もする(まあ、実際のところ MCP あたりが暴走すると危険なんですが)けれども、うまく使いこなせればこその「道具」なので、その道具を使いこなしていきましょう、というのがこの記事の主旨です。
バイブコーディング(vibe coding)とは?
ChatGPT にちまちまと関数のコードを貼り付けていたのととは違って、生成 AI を使って一気にコードを作成してしまおうというのが「バイブコーディング」の主な特徴です。いわゆる、AI にコードを書かせてしまって、コードを書く楽しみが…とも言えるのですが、職業プログラマとしては、
AI エージェントでは、Visual Studio Code + Claude Sonnet を使っています。GitHub Copilot ならば月10ドルで契約が可能です。学生ならば 0 円で利用ができます。他にも結構高めなものもありますが、夜中にぐるぐる回さない限りこれで十分です。Google の Gemini のほうは分からないのですが、どの AI エージェントを使ってもだいたい似た感じでいけます。好きなものを使ってください。
AI エージェントによっては、プロジェクトを自動作成するところからできるパターンも多いのですが、このように Visual Studio を使ってプロジェクトテンプレートを使ってひな形のプロジェクトだけ作っておくとよいです。ビルドも自動化できるのですが、結構面倒なので、AI ペアプロとしてビルドは人間のほうの担当にします。
これも慣れるとここまで書けるのですが、慣れないうちはそんなに細かく書かなくてもいいです。はっきり言って、概要とルールだけ書いても、このウマ娘レースは作成されてしまいます。どうやら、この手のゲームロジック(ブロック崩しとかインベーダーゲームとか)は生成 AI の学習データにあるっぽくて、それらしい何処かで見たようなものをうまく出してくれます。まあ、端的に言えばパクりなのですが、ここでは目をつぶっておきましょう。私的範囲内の利用ということです。
の3つに分かれています。この要素は agents.md に入れていないので、つまりはこれはどこからからパクって来たものです。こんな風に典型的な画面の作りの場合には AI エージェントが一番効率的に動きます。逆に、この3つの画面を、うまくひとつの画面に落とし込むのはなかなか苦労します。独自画面は苦手なんですよね。
7月頃だったか、新人研修が終わって AI エージェントがちょっと流行り始めた頃に1か月ほどあれこれと弄ってい時期があります。その頃は Claude Sonnet 4 を使って試していたのですが、いまだと Claude Opus 4.5 が使えるようになったので、もうちょっとマシになっているかなと思ってのお試しです。
別に Claude Code のプランモードが駄目という訳ではないのですが、IT 屋の範疇でいる “Plan” と、実務的な “Plan” とは大きな差があるので、そこだけ高速化したり自動化したりしても仕方がないんですよ。というのが本記事の主旨です。いわゆる、部分最適化に陥り勝ちになるので、じゃあ、全体を見渡した時に昨今の AI エージェントの Plan モードあるいは spec 駆動が、どのように実務に活用できるのか、という実験です。
プログラムを作るとか IT システムを組むとかいう範囲ではなくて、もう少し広い範囲で考えてみます。例えば、次のような要件での入札案件が出て来たとしましょう(実際に案件としてあるわけだし)。実際には入札までの設計とか見積もりとか人員の調達などが入るのですが、じゃあ、実務見積もりとしてどのくらい AI エージェントの機能が貢献できるのか? という実験をしてみます。これ、入口の部分は今書いている書籍に入ってはいるのですが、その続きはどうなるのかわからないので、結論はよくわからないところからスタートします。
正直言うとこの Plan モードってのは IT 屋特有の児戯に等しくてですね、実務に耐えられないと思うのです。
いわゆる、入札案件の要件定義を自分たちで書き出すのと、見通してとしての計画を立てておきます。このあたりは、経験が必要なので IT 業界で2,3年では難しいかもしれませんが、逆に言えば非ITの実務業界としては、このあたりがスタート地点になりますよね。さらっと、A4 の用紙に1枚で書けるようになるといいです。
で、これはたたき台なので、その前身として AI を使ってもかまいません。ただし、AI の提案するプランはフルスペックなものが多いので、適度に端折らなければいけません。いわゆる、適度な予算で組めるような計画を立てないいけないのです。コツとしては、松竹梅で作っておいて、竹になるように工夫するわけですが、そのあたりは別のノウハウですね。
軽く「進め方」を書いてありますが、これは非 IT 業界の人でも可能です。
なんらかの形で「成果物報告書」を収集する(手作業でも可)
なんらかの形で「成果物から内容をピックアップする(手作業…というか、人が読むのも可)
というわけで、大抵は人手で頑張ります。基本 IT 屋さんも人手に頼るところもあるのですが、そこは IT 業界の強みとして、何らかのツールを使って高速化/省力化していきたいです。さらに言えば、AI エージェントなどの力を借りて、労力を下げたり、ひとよりも精度をアップさせたいわけです。そこが、非 IT の人との差別化になりますよね。IT に関してはプロなわけですから、道具を活用できるように少し頭を働かせてみようというわけです。
これがプログラマだったり、プログラムを外注したりするとあれこれと設計やら打合せが必要になるのですが、AI エージェントががりがりとベタで作ってくれるので、その分手間が省けます。逆に言えば、ちょっと修正したい場合(自治体を増やしたい場合とか)は、Plan.prompt.md に自治体とリンク先を追加して、もういちど AI エージェントを使って実行して貰えばいいのです。
このあたりの繰り返しが非常に高速になったのが AI エージェントの利点です。どこのブログだったかツイートだったか忘れましたが「ウォーターフォール開発のプロセスを 15 分で廻せるようになった」のが大きいです。いままでは、この部分をアジャイル開発だとか計画駆動とかで設計を練る、あるいは動作させてから考え直す、というループが必要だったのですが、AI エージェントがすべてをやり直してくれるので「高速にまわる計画駆動」ができるようなったのが大きいですね。
まあ、この部分はPDF のダウンロード&分析という比較的やりやすいところをターゲットにしているので AI エージェントが有効に働きますが、これが部品の品質点検&改良とか、食品加工での調味料の組み合わせ試験、とかになると AI エージェントだけでは回らないので工夫が必要です。そういうときは別な計画が必要になります。他にも、組み込み機器で実測とか、Android 機器の BLE 相性あわせ(これをやっている)とかは AI エージェントだけでは回りません。
本来ならば、ピックアップしたい用語などを含めて PDF を OpenAI API に流し込んで、AI にスコア判定をさせる方法がよいです。これをやるためには、plan.prompt.md を書き変えて、評価の重みづけをするツールを Python スクリプトを作るのがよいです。このあたりは、プログラマの領分になってしまうので、またの機会にしましょう。
スコア計算のツールを AI エージェントを使って作るればいいだけなのですが、プロンプトの作成と OpenAI の API キーコードが必要になってしまうので、ちょっと非 IT 業界の人には荷が重いかもしれません。このあたり、自動化をしないのであれば、ChatGPT に PDF を手作業で突っ込みながらも可能なので、要件自体は満たせます。
final_report.md を python の md-to-pdf を使って PDF ファイルに直したものです。スコアをどう見るかは、中身に確認していかないと駄目なのですが、下調べ段階としてはこれで十分でしょう。すべてを調ベルのは大変なので、優先度としてスコアの高いものから順にチェックしていけばいいのです。そこは AI の判断を信用するかどうかは別なのですが。
まあ、こんな感じで非 IT 業界でも vscode + copilot の AI エージェントを使って資料作成とか情報収集とかができるようになりますよ、という例です。特に、この手のツールをを外注したり自前で時間をかけて作る必要がなくなるのが良いかなと。Plan のほうは、自分で計画立てないと途中で頓挫しそうですが、少しは頼りになるでしょう。
この内容を OpenAI のプロンプトと成果物の文書を一緒に渡して、評価結果を返して貰う事になります。以前は、システムプロンプトを作ったり、OpenAI API を呼び出す関数を作ったりと色々大変でしたが、いまでは AI エージェントが適切なコードを作ってくれます。
ここからはプログラマの領域に踏み込むので、非 IT 業界の方には少し辛いかもしれませんが、大丈夫です。概ねは AI エージェントがやってくれます。が、一発ではうまく動かない(大抵は JSON 形式が喰い違ってうまくいかない)のですが、これも AI エージェントが少しずつ直していってくれます。
このあたりが、AI エージェントが Excel VBA のように一般に広まるかどうかの肝ですね。いわゆる、バグ直しをしないといけないので、これは従来のプログラミングとほとんど変わりません。コードを書かない代わりに、不具合を見つけてうまく AI に指示を出すとか、自分が納得できるまで AI エージェントを働かせ続けることが必要になります。つまりは、根気がいるわけですが…ここはちょっと分かりません。私はプログラマなので慣れているのですが、一般の人はどう思うのか?
この手の話は、DALL-E の頃から言われていて、何かと数学的な図を書かせようとするとどこからかのなんちゃって画像を持ってくるために変なことになります。たぶん、古い教科書のスキャン画像とかを学習データにいれてしまっていて、そこから引っ張ってきているだけです。そもそも、画像の生成 AI に関しては、「教師なし学習」での推論でしかなくて、数学や物理のような自然科学的な解答のある「教師あり学習」の結果を求めようとしても無駄です。このあたりは、漫画やイラストを描かせた場合にはなんとなくいいけれど、部分的に手の指がおかしかったり腕が三本あったりするのがそれです。このあたりの正確性≒正解と明確に分かるものは、将来的にAIエージェントによる自己チェック機能で避けることができると思います。