181 lines
16 KiB
Markdown
181 lines
16 KiB
Markdown
# 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](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` の処理フロー
|
||
|
||
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-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](lineworks-anythingllm.js) — 本体
|
||
- [board-list.csv](board-list.csv) — sync のマスターリスト(flag=1が対象)
|
||
- [board-uploaded-hashes.json](board-uploaded-hashes.json) — 重複防止用のアップロード済みハッシュ一覧(10節)
|
||
- [run-lineworks-sync.bat](run-lineworks-sync.bat) — `sync` のダブルクリック実行用バッチファイル
|
||
- `board_posts_md/`, `board_attachments/` — 生成物の出力先(取り込み完了分は `complete/` 配下)
|