ken_nogi/lineworks-sync/lineworks-anythingllm.spec.md
Kenichiro NOGI 88a402ce0f up
2026-07-10 18:13:30 +09:00

16 KiB
Raw Blame History

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.csvflag 列を編集する。

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 の処理フロー

  1. アクセストークンを取得。
  2. board-list.csv から flag=1 の掲示板一覧を取得(loadTargetBoardsFromCsv)。0件ならアクセス可能な全掲示板を取得(fetchAllBoards)。対象が0件ならエラー終了。
  3. 対象掲示板を1件ずつ処理:
    1. fetchAllPosts でその掲示板の全投稿を取得(カーソルページング)。
    2. 投稿ごとに fetchPostDetail で詳細取得。fileCount > 0 なら fetchPostAttachments で添付ファイル一覧を取得。
    3. 投稿本文のハッシュ(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節)、投稿本文のハッシュを記録する。
    4. 添付ファイルは1件ずつ、そのハッシュ(10節)が未記録の場合のみ、リダイレクトを手動追跡しつつ本体をダウンロードし、ローカル保存後に個別に AnythingLLM へアップロードする。アップロード成功後、board_attachments/complete/ へ移動し(9.1節)、添付ファイルのハッシュを記録する。記録済みの場合はダウンロード自体を行わずスキップする。
    5. 投稿単位・掲示板単位でエラーが起きても、警告ログを出して次の投稿/掲示板の処理を継続する(スキップ処理)。
  4. アップロードした全ドキュメントの location をまとめて addToWorkspaceEmbeddings で embedding 登録(EMBEDDING_BATCH_SIZE 件ずつバッチ処理、失敗したバッチは警告ログに残し処理は継続)。
  5. 処理に失敗した掲示板があれば、最後に一覧として出力する(権限不足の可能性を案内)。

8.1 条文形式の分割ロジック

  • 第([0-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. 関連ファイル