16 KiB
16 KiB
lineworks-anythingllm.js 仕様書 (v3)
1. 概要
LINE WORKS の掲示板(board)投稿と添付ファイルを取得し、Markdown 化した上で AnythingLLM にアップロード・embedding 登録する RAG 連携スクリプト。認証は getGroupList.js と同じ、Node.js 標準の crypto モジュールによる JWT 自前署名方式を利用する(追加の npm install 不要)。
v3 では投稿本文に加え、PDF・画像等の添付ファイルも個別にダウンロードして AnythingLLM にアップロードする(AnythingLLM 側で OCR/テキスト抽出される)。
参考: LINE WORKS 掲示板 API 仕様書 https://developers.worksmobile.com/jp/docs/board
2. 実行環境・依存パッケージ
- Node.js 18 以上(標準
fetch/FormData/Blobを使用) - 追加の npm パッケージは不要
- 実行例:
node lineworks-anythingllm.js <コマンド> - ダブルクリック実行用: run-lineworks-sync.bat(
syncを実行して自動終了)
3. コマンド一覧
| コマンド | 内容 |
|---|---|
node lineworks-anythingllm.js sync |
board-list.csv の対象掲示板(または全掲示板)の投稿・添付ファイルを取得し、AnythingLLM に同期する。 |
node lineworks-anythingllm.js list |
アクセス可能な全掲示板一覧を取得し、board-list_<日時>.csv に出力する(sync対象の選定用の一覧出し)。 |
node lineworks-anythingllm.js ask "問い合わせ内容" |
AnythingLLM ワークスペースに質問し、回答と参照元投稿を表示する。 |
node lineworks-anythingllm.js test-recent |
動作確認用。掲示板を指定せず直近の新着投稿を横断取得できるか確認する。 |
4. 環境変数
LINE WORKS 認証系(getGroupList.js と共通の命名)
| 変数名 | 既定値 | 用途 |
|---|---|---|
LW_CLIENT_ID |
(コード内既定値あり) | LINE WORKS API クライアントID |
LW_CLIENT_SECRET |
(コード内既定値あり) | クライアントシークレット |
LW_SERVICE_ACCOUNT |
(コード内既定値あり) | Service Account のメールアドレス |
LW_PRIVATE_KEY |
— | 秘密鍵の中身を直接渡す場合に使用(未設定なら LW_PRIVATE_KEY_FILE を使用) |
LW_PRIVATE_KEY_FILE |
./private_20260307184804.key |
秘密鍵ファイルのパス |
LW_SCOPE |
board board.read |
取得スコープ。掲示板APIには board、投稿本文の読み取りには board.read が別途必要な場合がある |
LW_REQUEST_DELAY_MS |
250 |
API呼び出し間のスロットリング間隔(ms) |
LW_MAX_RETRIES |
5 |
429(Rate Limit)発生時の最大リトライ回数 |
AnythingLLM 系
| 変数名 | 既定値 | 用途 |
|---|---|---|
ANYTHINGLLM_BASE_URL |
— | 例: http://localhost:3001 |
ANYTHINGLLM_API_KEY |
— | APIキー |
ANYTHINGLLM_WORKSPACE_SLUG |
— | 例: lineworks-board |
EMBEDDING_BATCH_SIZE |
50 |
embedding登録のバッチサイズ |
AI分割(LM Studio)系
| 変数名 | 既定値 | 用途 |
|---|---|---|
AI_SPLIT_MIN_LENGTH |
3000 |
この文字数を超える非条文形式の投稿はAI分割を試みる |
LMSTUDIO_CHAT_URL |
http://localhost:1234/v1/chat/completions |
LM Studio のチャット補完エンドポイント |
LMSTUDIO_MODEL_NAME |
qwen2.5-14b-instruct |
使用モデル名 |
5. board-list.csv(同期対象の管理)
boardId,boardName,flag
4080000000758656266,全社掲示板(通達),1
4080000000768413725,全社掲示板(お知らせ)
- スクリプトと同じフォルダに配置。
flag列が"1"の行だけがsyncの対象になる。- 該当行が1件も無い場合(ファイルが無い場合を含む)は、アクセス可能な全掲示板を自動取得して同期する(csv-anythingllm.js の
csv-list.csvとは異なり、こちらは全件フォールバックする仕様)。 listコマンドはこのファイルを直接更新せず、board-list_<実行日時>.csvという別ファイルに現在アクセス可能な全掲示板を出力する。同期対象を追加/変更したい場合は、その内容を見ながら手動でboard-list.csvのflag列を編集する。
6. 認証・トークン管理
- JWTアサーション(RS256)を
crypto.createSignで自前署名し、urn:ietf:params:oauth:grant-type:jwt-bearerグラントで LINE WORKS のトークンエンドポイントからアクセストークンを取得する(getAccessToken)。 - 19桁の
boardId/postIdは JSON.parse の数値精度落ちを避けるため、パース前に該当キーの数値を文字列化してからJSON.parseする(parseJsonWithQuotedInt64)。 - アクセストークンの有効期限は1時間。
sync実行中は45分経過ごとに自動的に再取得する(ensureFreshToken/TOKEN_REFRESH_INTERVAL_MS)。長時間の同期処理中に401で失敗するのを防ぐための仕組み。
7. Rate Limit対策
- 全ての LINE WORKS API 呼び出しは共通ラッパー
lwFetchを経由する。 - 呼び出し前に
LW_REQUEST_DELAY_MS(既定250ms)のウェイトを入れてスロットリングする。 - 429(Rate Limit)応答時は
Retry-Afterヘッダー、無ければ指数バックオフで待機し、LW_MAX_RETRIES(既定5回)まで自動リトライする。
8. sync の処理フロー
- アクセストークンを取得。
board-list.csvからflag=1の掲示板一覧を取得(loadTargetBoardsFromCsv)。0件ならアクセス可能な全掲示板を取得(fetchAllBoards)。対象が0件ならエラー終了。- 対象掲示板を1件ずつ処理:
fetchAllPostsでその掲示板の全投稿を取得(カーソルページング)。- 投稿ごとに
fetchPostDetailで詳細取得。fileCount > 0ならfetchPostAttachmentsで添付ファイル一覧を取得。 - 投稿本文のハッシュ(10節)が
board-uploaded-hashes.jsonに記録済みならアップロード処理をスキップする。未記録の場合のみ、本文(HTMLタグ除去後)を以下の優先順で処理:- 条文形式(「第◯条」が3件以上かつ本文3000文字以上、8.1節): 条ごとに個別 Markdown ファイルに分割してアップロード。
- 非条文の長文(3000文字超、8.2節): LM Studio に区切り位置だけ判断させ、区切り記号の位置でプログラム側が機械的に分割してアップロード(本文の改変は許容誤差±10%を超えたら破棄し通常アップロードにフォールバック)。
- 通常の投稿: 1投稿1 Markdown ファイルとしてアップロード。
アップロード成功後、そのファイルを
board_posts_md/complete/へ移動し(9.1節)、投稿本文のハッシュを記録する。
- 添付ファイルは1件ずつ、そのハッシュ(10節)が未記録の場合のみ、リダイレクトを手動追跡しつつ本体をダウンロードし、ローカル保存後に個別に AnythingLLM へアップロードする。アップロード成功後、
board_attachments/complete/へ移動し(9.1節)、添付ファイルのハッシュを記録する。記録済みの場合はダウンロード自体を行わずスキップする。 - 投稿単位・掲示板単位でエラーが起きても、警告ログを出して次の投稿/掲示板の処理を継続する(スキップ処理)。
- アップロードした全ドキュメントの location をまとめて
addToWorkspaceEmbeddingsで embedding 登録(EMBEDDING_BATCH_SIZE件ずつバッチ処理、失敗したバッチは警告ログに残し処理は継続)。 - 処理に失敗した掲示板があれば、最後に一覧として出力する(権限不足の可能性を案内)。
8.1 条文形式の分割ロジック
第([0-90-9]+)条を条区切りとして正規表現で検出(全角/半角数字対応)。見出しが括弧内にあれば取得。- 本文が3000文字以上、かつ条文パターンが3件以上マッチした場合のみ分割対象とする(
ARTICLE_SPLIT_MIN_LENGTH/ARTICLE_SPLIT_MIN_COUNT)。それ未満は通常の1投稿1ファイル扱い。 - 最初の条文より前に50文字を超える前文がある場合は「前文」として別ファイルに分離する。
- LLMは使用せず、正規表現による機械的な分割のみ(本文の書き換え・要約は行わない)。
8.2 非条文長文のAI分割ロジック
- 対象: 条文形式に該当せず、本文が
AI_SPLIT_MIN_LENGTH(既定3000文字)を超える投稿。 - LM Studio(OpenAI互換のchat completions API)に対し、「本文は一切変更せず、分割したい箇所にのみ
===SPLIT===を挿入して返す」プロンプトを送信。 - 返却された本文を
===SPLIT===で分割し、2件未満(=分割されなかった)場合は通常の1ファイル扱いにフォールバック。 - 改変チェック: 分割後の合計文字数が原文の90%〜110%の範囲を外れた場合は分割結果を破棄し、通常アップロードにフォールバックする(LLMによる意図しない要約・改変を防ぐガード)。
9. Markdown/添付ファイルの出力先
| 種別 | 出力先 | ファイル名規則 |
|---|---|---|
| 投稿(通常) | board_posts_md/ |
board<boardId>_post<postId>.md |
| 投稿(条文分割) | board_posts_md/ |
board<boardId>_post<postId>_art<条番号>.md |
| 投稿(AI分割) | board_posts_md/ |
board<boardId>_post<postId>_part<連番>.md |
| 添付ファイル | board_attachments/ |
board<boardId>_post<postId>_<attachmentId>_<安全化したファイル名> |
MIMEタイプは拡張子から簡易判定(guessMimeType。pdf/png/jpg/gif/webp/txt/md/docx/xlsx/pptx/csv に対応、それ以外は application/octet-stream)。
9.1 取り込み完了ファイルの退避(complete フォルダ)
AnythingLLMへのアップロードが成功したファイルは、共通関数 moveFileToCompleteDir で元のディレクトリ配下の complete/ サブフォルダへ移動する(次回 sync での重複アップロード・重複整理を避けるため)。
| 対象 | 移動先 |
|---|---|
| 投稿Markdown(通常/条文分割/AI分割いずれも) | board_posts_md/complete/ |
| 添付ファイル | board_attachments/complete/ |
- 同名ファイルが移動先に既に存在する場合は上書きせず、
<ファイル名>_<タイムスタンプ>.<拡張子>の形式で退避する。 - アップロードが失敗した(location が取得できなかった)ファイルは移動されず、
board_posts_md/・board_attachments/直下に残る。
10. 重複防止の仕組み
board-uploaded-hashes.json(スクリプトと同じフォルダ)に、アップロード済みの投稿・添付ファイルのSHA-256ハッシュを記録し、次回以降の sync で同一内容の再アップロードをスキップする(csv-anythingllm.js の行ハッシュ方式と同じ考え方)。
| 対象 | ハッシュの元になる値 | 備考 |
|---|---|---|
| 投稿本文 | boardId + postId + 更新日時(updatedTime、無ければcreatedTime) + タイトル + 本文 |
本文が編集され更新日時が進むと別ハッシュになり、再アップロードされる。 |
| 添付ファイル | boardId + postId + attachmentId |
attachmentId はLINE WORKS側で不変のため、内容ハッシュではなくID自体で同一性を判定する。ダウンロード自体もスキップされる。 |
- 投稿本文と添付ファイルの重複判定は独立している。投稿本文が重複でスキップされても、その投稿に新しく追加された未アップロードの添付ファイルは個別に取り込まれる。
- ハッシュはアップロード成功の都度、即座に
board-uploaded-hashes.jsonへ保存する(途中で異常終了しても再実行時に重複しないようにするため)。 - 実行末尾に「重複スキップ: 投稿n件 / 添付ファイルn件」件数をログ出力する。
- 本機能導入前にアップロード済みだった投稿・添付ファイル(初回導入時に
complete/へ手動退避したものを含む)はハッシュが未記録のため、導入後最初のsyncでは新規として再アップロードされる(13節も参照)。
11. list コマンド
- アクセス可能な全掲示板を取得し、
board-list_<実行日時(YYYYMMDD-HHMMSS)>.csvとして出力(UTF-8 BOM付き)。 - 値にカンマ・改行・ダブルクォートを含む場合は
csvEscapeによりダブルクォートで囲みエスケープする(board-list.csvを読み込む側のloadTargetBoardsFromCsvは単純なsplit(",")のためエスケープに対応していない点に注意 — 掲示板名にカンマ等が含まれる場合はboard-list.csv側で手動調整が必要)。
12. ask コマンド
- 指定した質問文を AnythingLLM ワークスペースの
/chat(mode: "query")に送信し、回答本文(textResponse)と参照元(sources)のタイトル/ファイル名を表示する。
13. ログ・エラーハンドリング
- 専用のログファイル出力機構は無く、標準出力(
console.log/warn/error)にのみ出力する(csv-anythingllm.jsのようなファイルロギングは無い)。 - 投稿単位・掲示板単位のエラーは警告として記録しつつ処理を継続する(スキップ)。
- アクセストークン取得やLINE WORKS API呼び出し自体の失敗(認証情報不備、権限不足など)は例外として送出され、
main()のcatchでエラーメッセージを表示して終了コード1で終了する。
14. 制限事項・注意点
board-list.csvが無い/対象0件の場合は全掲示板を自動取得するため、想定外の掲示板まで同期されないよう、定常運用ではboard-list.csvを用意し明示的に対象を絞ることを推奨する。- AI分割機能はローカルの LM Studio(既定
http://localhost:1234)が起動している前提。未起動の場合はAI分割時にエラーとなり、警告ログを出した上で通常アップロードにフォールバックする。 - 添付ファイルのダウンロードは LINE WORKS のリダイレクト先(
apis-storage.worksmobile.com)へも同じBearerトークンを引き継ぐ必要があるため、fetchの自動リダイレクトではなく手動でリダイレクトを処理している(redirect: "manual")。 board_posts_md/・board_attachments/のcomplete/サブフォルダは、fetchAllPosts/fetchPostAttachmentsの取得結果とは無関係にローカルファイルの整理のみを行うものであり、board-uploaded-hashes.jsonへの記録とは別軸の仕組みである。complete/へ移動済みでもハッシュが未記録であれば、次回syncで再度取得・再アップロードされローカルファイルも再生成される。
15. 関連ファイル
- lineworks-anythingllm.js — 本体
- board-list.csv — sync のマスターリスト(flag=1が対象)
- board-uploaded-hashes.json — 重複防止用のアップロード済みハッシュ一覧(10節)
- run-lineworks-sync.bat —
syncのダブルクリック実行用バッチファイル board_posts_md/,board_attachments/— 生成物の出力先(取り込み完了分はcomplete/配下)