とある事情で、Wordに自動でコメントを入れたいという要望があったので、試してみました。

WordのCopilotやGoogleドキュメントのGeminiでコメントの挿入をお願いすると、内部で利用しているpython-docx にその機能がなくてできないよと言われます。
仕方ないので、別の方法を検討します。
今時だと、Coworkとかを使うのでしょうが、ローカルのLM Studioでどこまでできるかを試すため、Officeアドインを使って試してみることにしました。
Officeアドインには、Script Labというプロトタイプを開発するための仕組みがあります。 これを使うと、簡単にローカルで動作するアドインがつくれます。
今回はこれを使って試してみることにします。
セットアップ
Script Labを入れます。
あとは、LM Studioとモデルをインストールしておきましょう。 今回は、qwen/qwen3-4b-2507を使いました。
Wordでコードを書く
Wordファイルを開くと、Script Labメニューが出現していますので、クリックします。
リボンメニューからCodeを選択します。
Scriptでコードエディタが開くので、コードを書きます。
ローカルLLMを呼び出して、分割して文章を入力し、得た出力をコメントとして挿入するコードです。 ChatGPT APIなどを使えば、分割せずに一気に文章を入力することも可能でしょう。
const PARAGRAPHS_PER_CHUNK = 10; // ← 分割数(段落数)
document.getElementById("run").addEventListener("click", () => tryCatch(run));
async function run() {
await Word.run(async (context) => {
const paragraphs = context.document.body.paragraphs;
paragraphs.load("items");
await context.sync();
// 1) 全段落テキストをロード(index整合性を優先)
const items = paragraphs.items;
for (const p of items) p.load("text");
await context.sync();
const paraTexts = items.map(p => (p.text ?? "").trim());
// 2) 段落配列を N 個ずつに分割
const chunks = splitIntoChunks(paraTexts, PARAGRAPHS_PER_CHUNK);
// 3) 各チャンクをAPIに投げ、paragraphIndexを補正して集約
const allComments = [];
for (const chunk of chunks) {
const { startIndex, paragraphs: chunkParas } = chunk;
const comments = await proofreadWholeDocByLocalOpenAICompat(chunkParas);
if (!Array.isArray(comments)) continue;
for (const c of comments) {
if (typeof c ?.paragraphIndex !== "number") continue;
// ★ オフセット補正(ここが重要)
allComments.push({
paragraphIndex: c.paragraphIndex + startIndex,
findings: c.findings,
});
}
}
if (allComments.length === 0) {
console.log("No comments returned.");
return;
}
// 4) Wordにコメント挿入
for (const c of allComments) {
const pi = c.paragraphIndex;
const findings = c.findings;
if (pi < 0 || pi >= items.length) continue;
if (!Array.isArray(findings) || findings.length === 0) continue;
const commentText = findings
.filter(f => f && typeof f.message === "string" && f.message.trim())
.map(f => `• ${f.message}${f.severity ? `(${f.severity})` : ""}`)
.join("\n");
if (!commentText) continue;
items[pi].getRange().insertComment(commentText);
}
await context.sync();
console.log("Done: comments inserted.");
});
}
/**
* 段落配列を N 個ずつに分割し、元indexを保持
*/
function splitIntoChunks(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push({
startIndex: i, // 元の段落index
paragraphs: array.slice(i, i + size),
});
}
return chunks;
}
/**
* チャンク(段落配列)を1回で校正
* 戻り値:
* [
* { paragraphIndex: number, findings: [{type,message,severity?}] }
* ]
*/
async function proofreadWholeDocByLocalOpenAICompat(paragraphs) {
const url = "http://localhost:1234/v1/chat/completions";
const model = "model-identifier";
const system = `
あなたは日本語論文の校正者です。
入力は「文書の段落配列 paragraphs」です。
各段落について、日本語として不自然/論文として不適切/曖昧な表現を指摘してください。
修正案(書き換え文章)は出さないでください。
必ず次のJSON“だけ”で返してください。説明文は禁止。
{
"comments": [
{
"paragraphIndex": 0,
"findings": [
{"type":"grammar|style|logic|term|ambiguity", "message":"短く具体的な指摘", "severity":"low|mid|high"}
]
}
]
}
- 指摘がない段落は comments に含めないでください。
- paragraphIndex は paragraphs の 0始まり添字です。
`.trim();
const user = JSON.stringify({ paragraphs }, null, 2);
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model,
messages: [
{ role: "system", content: system },
{ role: "user", content: user },
],
temperature: 0.2,
}),
});
const data = await res.json();
const answer = data ?.choices ?.[0] ?.message ?.content ?? "";
const parsed = safeJsonParse(answer);
if (parsed && Array.isArray(parsed.comments)) {
return parsed.comments.filter(
x => typeof x ?.paragraphIndex === "number" && Array.isArray(x ?.findings)
);
}
// フォールバック(JSON崩れ)
if (answer.trim()) {
return [{
paragraphIndex: 0,
findings: [{ type: "raw", message: answer.trim(), severity: "mid" }]
}];
}
return [];
}
function safeJsonParse(s) {
try {
const cleaned = s
.replace(/^```json\s*/i, "")
.replace(/^```\s*/i, "")
.replace(/```$/i, "")
.trim();
return JSON.parse(cleaned);
} catch {
return null;
}
}
async function tryCatch(callback) {
try {
await callback();
} catch (error) {
console.error(error);
}
}
Wordでプロトタイプとして作成したアドインを実行する
▷ボタンを押します。
Runボタンが出てくるので、押します。
これで書いたコードがアドインとして実行されます。 コメントがローカルLLMで自動生成され、スクリプトで自動で挿入されるようになりました。
いくつか試してみましたが、それっぽいコメントは挿入してくれるものの、やっぱりそのまま使うのは厳しそうな感じでした。 ただ、精度は他のAIを使えば改善できますので、仕組みとしては十分使えそうに思いました。
他のOfficeアプリであるExcelやPowerPointなどでも同じ仕組みが使えると思いますので、どうしてもOfficeファイルを自動で操作したい、といった場合には、選択肢の1つとして検討してみるのもありかと思います。