最近、Excel で選択したセルの内容を Ollama で要約・添削できる Add-in を作ってみた。
正直なところ、想定外の落とし穴がいっぱいあった。特に「ローカルネットワーク別 PC の Ollama に、Excel Add-in から安全にアクセスする」という部分が、予想以上に複雑だった。その試行錯誤プロセスを記録しておく。
背景:なぜこんなもの作ったのか
仕事で長めの文章を扱うことが多い。メール、レポート、提案書とか。
最初は ChatGPT API で要約・添削をやってみたけど、3 つの理由でやめた:
- データを外に出したくない - 機密文書を API に送るのは避けたい
- API コストが地味に積もる - 毎日使うと月額が結構かかる
- レスポンス待ちが遅い - 社内 LAN 上なら圧倒的に速い
「Excel から直接使えたら最高だな」と思いついて、Add-in を作り始めたのが始まり。
技術構成
実装してみた構成は以下:
【Excel Add-in】 ↓ HTTPS でロード https://localhost:8888(React UI の配信) ↓ HTTP リクエスト 【Go バックエンド】(localhost:8888) ↓ ローカルネットワーク経由 【Ollama API】(http://172.19.10.10:11434)
バックエンド: Go(標準 net/http パッケージ)
- シンプルに HTTP サーバーを立てるだけなので、フレームワークは不要だと判断
- 自己署名証明書での HTTPS 対応
フロント: React
- Office JavaScript API で Excel セルの読み書き
- Go API への HTTP 通信
Ollama
- ローカルネットワーク別 PC 上で実行
- セルの内容を送信 → 要約・添削結果を取得
予想外の落とし穴:Excel Add-in の HTTPS 必須ポリシー
実装し始めてすぐにぶつかったのが、Excel Add-in は HTTP ではなく HTTPS が必須という制約。
「localhost で動かすんだから HTTP でいいか」と思ってた。甘かった。
さらに複雑だったのが、Ollama が ローカルネットワーク別 PC にあること。
構図:
PC A(Excel を使ってる)
↓
自分の PC(Go + React のサーバー)
↓ ローカルネットワーク
PC B(172.19.10.10 に Ollama が動いてる)
やりたいこと:
- Excel Add-in の UI(HTML/React)を自分の PC から HTTPS で配信
- Add-in から Ollama(別 PC)に HTTP でアクセス
問題:
- Excel Add-in は HTTPS 配信が必須だけど、自己署名証明書では「信頼されていない」と判定される
- 別 PC の Ollama にはネットワーク経由でアクセスする必要がある
解決策 1:オレオレ証明書を localhost で動かす
まず自己署名証明書を生成した。
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes \
-subj "/CN=localhost"
この証明書と秘密鍵で、Go の net/http サーバーを立ち上げる。
package main import ( "net/http" ) func main() { // ハンドラーを登録 http.HandleFunc("/api/summarize", handleSummarize) http.HandleFunc("/api/proofread", handleProofread) // HTTPS で起動(自己署名証明書使用) http.ListenAndServeTLS(":8443", "server.crt", "server.key", nil) }
これで https://localhost:8443 で HTTPS サーバーが起動。
ただし、ここまでではまだ不十分。Excel Add-in のコンテキストでは、この自己署名証明書が「信頼されていない」と判定される。
解決策:自己署名証明書 + ネットワークルーティング
ここが工夫の見せどころ。
2 つのポイントで解決した:
- localhost で自己署名証明書を使った HTTPS サーバー起動 - Excel Add-in の HTTPS 必須要件を満たす
- Go バックエンド - HTML/React を配信しつつ、Ollama への API リクエストを中継
【構図】
Excel Add-in
↓ HTTPS でロード
https://localhost:8888
↓ HTML/React の配信 + API エンドポイント
【Go サーバー】
├─ /(HTML/React UI を配信)
└─ /api/summarize, /api/proofread(Ollama に転送)
↓ HTTP でローカルネットワーク経由
http://172.19.10.10:11434
↓
【Ollama API】
実装
まず自己署名証明書を生成。
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes \
-subj "/CN=localhost"
Go のサーバーコード:
package main import ( "net/http" "net/http/httputil" "net/url" ) func main() { // Ollama API へのリバースプロキシを設定 ollamaURL, _ := url.Parse("http://172.19.10.10:11434") reverseProxy := httputil.NewSingleHostReverseProxy(ollamaURL) // HTML/React UI を配信 fs := http.FileServer(http.Dir("./public")) http.Handle("/", fs) // /api/generate は Ollama に中継 http.HandleFunc("/api/generate", func(w http.ResponseWriter, r *http.Request) { reverseProxy.ServeHTTP(w, r) }) // HTTPS で起動(自己署名証明書) http.ListenAndServeTLS(":8888", "server.crt", "server.key", nil) }
React 側から Ollama API を呼び出すときは、相対パス /api/generate を使う。
// React コンポーネント const handleSummarize = async (cellContent) => { const response = await fetch('/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'llama2:7b', prompt: `要約してください:\n\n${cellContent}`, stream: false }) }); const result = await response.json(); return result.response; };
重要なポイント:
- localhost:8888 で自己署名証明書を使った HTTPS サーバーを起動
- Excel Add-in のセキュリティ要件(HTTPS 配信)を満たす
- React は相対パス
/api/generateで Ollama に通信(実際には Go バックエンドが仲介) - Go バックエンド内で、localhost から 172.19.10.10 へのネットワークルーティングを実現
- シンプル:プロキシツール不要で、Go だけで実装完結
セルを選択→要約・添削:実装の流れ
実装が決まったら、次は実用機能。
Excel で文章が入ってるセルを選択して、「要約」「添削」ボタンを押すと、Ollama で処理してセルに結果を返す。
流れ:
- セル選択時のイベント取得
await Excel.run(async (context) => { const cell = context.application.getSelectedData(); // セルの内容を取得 });
- Go API に送信
const summarized = await fetch('https://localhost:8888/api/summarize', { method: 'POST', body: JSON.stringify({ text: cellContent }) });
- Ollama から要約結果を取得
- モデルはデフォルトで
llama2:7bを使用 - プロンプトエンジニアリングで「要約」「添削」を区別
- 結果をセルに書き込み
await Excel.run(async (context) => { context.worksheets.getActiveWorksheet() .getRange("A1") .values = [[result]]; });
実装時の工夫:
- ローディング表示 - Ollama はレスポンスに数秒かかるので、「処理中...」を表示
- エラーハンドリング - ネットワーク接続や Ollama が落ちてる場合の対応
- タイムアウト設定 - 長すぎる文章は処理に時間がかかるので、上限を設ける
- プロンプト最適化 - 同じモデルでも「要約」「添削」でプロンプトを変える
// 要約用プロンプト func getSummarizePrompt(text string) string { return fmt.Sprintf( "以下のテキストを簡潔に要約してください。箇条書きで3点まで。\n\n%s", text, ) } // 添削用プロンプト func getProofreadPrompt(text string) string { return fmt.Sprintf( "以下のテキストの文法、表現、敬語を確認して、改善案を提示してください。\n\n%s", text, ) }
実際に使ってみて
完成して 1 ヶ月ほど使ってみた感想。
便利だったこと:
- データが外に出ない - 機密文書を扱うときは本当に安心。API に送ってる時間を気にしなくていい
- レスポンスが速い - ローカルネットワーク経由なので、ChatGPT API より圧倒的に高速。待ち時間がストレスにならない
- コストがゼロ - Ollama はローカル実行なので追加コストなし。PC のリソース(GPU)を使ってる分だけ
予想外だったこと:
- 要約の質が思ったより良い - llama2:7b で十分実用的。わざわざ大型モデルに乗り換える必要がない
- 添削より要約の方が使用頻度が高い - 最初は「添削機能メインで作ろう」と思ってたけど、実用では「要約」がメイン用途に
- 複数人での利用が想定より難しい - 自分の PC でプロキシを立ててるので、他の PC からはアクセスできない。チーム展開するなら、プロキシを別サーバーに移す必要があった
今後の改善案:
- Ollama の複数モデル対応 - llama2 以外の軽量モデル(mistral など)も選べるようにしたい
- Web UI での設定画面 - 今は Go コード内でハードコードしてる設定を、UI で変更できるように
- ローカルプロキシをサーバー化 - チーム利用を想定して、プロキシを別サーバーに移す
技術的な学び
このプロジェクトで学んだことが 3 つ。
1. Excel Add-in のセキュリティ要件は思ったより厳しい
Excel が HTTPS・自己署名証明書・CORS まで厳密にチェックする。SPA(Single Page Application)の世界と違って、企業向けアプリケーションの厳格さを感じた。
2. ローカル接続は「仲介役」として超有効
別ネットワークにある Ollama にアクセスする時、直接アクセスではなくローカルサーバー経由にすることで、セキュリティと利便性のバランスが取れた。
3. ローカル LLM の強さ
API の待ち時間がゼロに等しく、コストもかからない。確度は ChatGPT より落ちるかもしれないが、実務用途(要約・添削)には十分。「LLM の大型化よりローカル化の時代が来たのかも」と感じた。
まとめ
「Go + React + Ollama」という組み合わせで Excel Add-in を作った。
最大の工夫は「自己署名証明書 + ネットワークルーティング」という組み合わせで、ローカルネットワーク別 PC の Ollama に安全にアクセスする仕組み。
完成してみると、想定外の利便性があった。特に「データが絶対外に出ない」という安心感と「レスポンスの速さ」は、クラウド API では代替できない価値。
チーム展開や本番運用となると、プロキシのサーバー化やアーキテクチャの見直しが必要だけど、個人ツールレベルなら「自己署名証明書 + ネットワークルーティング」で十分実用的。
ローカル LLM の活用方法は、これからも増えていくんだろうなと思った。