Files
playchat/docs/api.md
T
colcaroliandClaude Opus 5 97568ba90e Re-embed the knowledge base when the embedding model changes
Vectors from two different embedding models are not comparable — Titan v2 has
1024 dimensions where nomic-embed-text has 768 — and _cosine_similarity zips
them, so a mismatch produced quietly meaningless scores rather than an error.
Nothing forced a re-embed either: sync_local_kb only looked at the content
hash, so switching models left every cached vector in place.

local_knowledge_docs now records the model each vector came from, and a row is
re-embedded when its content hash OR its model differs. Saving a new
LOCAL_KB_EMBEDDING_MODEL from the settings panel starts a background pass over
every file; the knowledge base panel also has a button for it. While that pass
runs, the per-request sync steps aside so no file is embedded twice.

The panel now shows what the embedding model is doing: model name, vector
size, how many files are embedded with the current model, how many are still
on an older one, a progress bar while a pass runs, and the last error. A
"接続テスト" button embeds one short string so a broken or unreachable model
can be told apart from one that is merely slow to warm up.

Also read TAVILY_API_KEY from os.environ at call time. It was imported as a
config constant, which is evaluated before load_into_env() runs, so a key
saved from the settings panel was stored but never used — not even after a
restart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 22:56:12 +09:00

12 KiB
Raw Blame History

API リファレンス

ベースURL: http://localhost:8000
Swagger UI: http://localhost:8000/docs


チャット

POST /chat/stream

SSE でストリーミング応答を返す。

リクエストボディ

{
  "message": "こんにちは",
  "session_id": "uuid(省略時は新規セッション作成)",
  "model": "gpt-5.4(省略時は DEFAULT_MODEL)",
  "note_page_ids": [1, 2]
}

note_page_ids は「このセッションで本文をシステムプロンプトに載せる共有ノートのページ ID」。 渡すとコンテキスト組み立ての前にサーバー側の指定を丸ごと置き換える(PUT /chat/sessions/{id}/notes と同じ内容)。 新規セッションではフロントがまだ session_id を持てないため、初回はここでしか指定できない。省略すると現在の指定を変更しない。

SSE イベント

type value タイミング
session_id セッション UUID 新規セッション作成直後
chunk テキスト断片 LLM トークン生成ごと
tool {id, name, label, detail, status, result} ツール実行の開始時 (status: running) と完了時 (status: done)。同じ id で 2 回届くのでフロントはマージして表示する
sources [{title, url}] Web 検索した時のみ(出典一覧)
notes_updated — AI が共有ノートを書き換えた直後
done — 正常完了
error エラーメッセージ 例外発生時

GET /chat/sessions

セッション一覧を updated_at 降順で返す。

レスポンス

[
  {
    "id": "uuid",
    "title": "セッションタイトル",
    "model": "gpt-5.4",
    "updated_at": "2026-06-01T12:00:00"
  }
]

GET /chat/sessions/{session_id}/messages

セッションのメッセージ一覧を created_at 昇順で返す。要約に取り込まれた(アーカイブ済みの) メッセージも含めて返し、archived_at で区別する。アーカイブ済みのものは LLM に渡す コンテキストには入らないが、履歴としては残っている。

レスポンス

[
  { "id": 1, "role": "user", "content": "...", "created_at": "...", "archived_at": "2026-08-20T04:00:00+00:00", "tags": [] },
  { "id": 2, "role": "assistant", "content": "...", "created_at": "...", "archived_at": null, "tags": [] }
]

GET /chat/sessions/{session_id}/notes

このセッションで本文をシステムプロンプトに載せるよう指定された共有ノートのページ ID を返す。 順序はノートのタブ順(position)。セッションが無ければ 404。

レスポンス

{ "page_ids": [1, 2] }

PUT /chat/sessions/{session_id}/notes

指定を丸ごと置き換える。存在しないページ ID は黙って捨てられ、実際に紐付いた ID が返る。 空配列を渡すと全解除。セッションが無ければ 404。

リクエスト

{ "page_ids": [1, 2] }

DELETE /chat/sessions/{session_id}

セッションと紐づくメッセージ・メモリをすべて削除。204 No Content を返す。


メモリ

GET /memory/{session_id}

セッションの長期メモリ(要約)一覧を created_at 昇順で返す。

レスポンス

[
  {
    "id": 1,
    "session_id": "uuid",
    "summary": "要約テキスト",
    "created_at": "2026-06-01T12:00:00"
  }
]

DELETE /memory/{memory_id}

特定のメモリエントリを削除。204 No Content を返す。


共有ノート

ユーザーと AI が共同編集するノート。ページのタイトル一覧は毎回システムプロンプトに注入され、 本文は AI が read_note / write_note ツールで読み書きする。 section を設定すると、同じセクション名のページはタブバー上で常に連続してグループ表示される。

GET /api/notes

全ページを position 昇順で返す。同じ section のページは常に連続する(隣接=同一グループとして扱われる)。

レスポンス

[
  {
    "id": 1,
    "title": "買い物リスト",
    "content": "- 牛乳
- 卵",
    "position": 0,
    "section": "生活",
    "updated_at": "2026-08-19T12:00:00"
  }
]

POST /api/notes

ページを新規作成し、201 Created で作成したページを返す。 section を指定すると、作成直後にそのセクションの末尾へ配置される(他ページの position も再採番される)。

リクエスト

{ "title": "新しいページ", "content": "", "section": null }

PATCH /api/notes/{page_id}

ページのタイトル・本文・セクションを更新する。指定したフィールドだけが変更される。 タイトルを空文字にしようとした場合は 400、ページが存在しない場合は 404 を返す。

section はリクエストボディにキーとして含まれているかどうかで判定する(null を明示的に送るとグループ解除)。 セクションを変更すると、そのページは新しいセクションの末尾(該当ページが無ければ末尾)へ移動し、 全ページの position が振り直される。

リクエスト

{ "content": "更新後の本文", "section": "生活" }

POST /api/notes/sections/rename

old_name セクションに属する全ページを new_name セクションへ一括で付け替える。 new_name を空文字/未指定にすると、そのセクションの全ページのグループを解除する。 ページの並び順(position)は変更しない。

リクエスト

{ "old_name": "生活", "new_name": "生活雑貨" }

レスポンス

{ "renamed": 2 }

POST /api/notes/reorder

タブのドラッグ&ドロップによる並べ替えを反映する。order には全ページIDをドラッグ後の 新しい順序で渡す(サーバー側では計算しない。フロントが store.pages から算出する)。 page_id のページには先に section を設定してから、order の順に position を振り直す。 別セクションのタブの前後にドロップした場合は、そのセクションへの移動も兼ねる。

order が現在の全ページIDの集合と一致しない場合は 400 を返す。

リクエスト

{ "page_id": 3, "section": "生活", "order": [1, 3, 2, 4] }

レスポンス

{ "reordered": 4 }

DELETE /api/notes/{page_id}

ページを削除。204 No Content を返す。


POST /api/notes/import

multipart/form-data の files でアップロードされたファイルをページとして取り込み、 201 Created で作成したページの配列を返す。

拡張子 取り込み方
.md / .markdown / .txt / .text 1 ファイル 1 ページ。タイトルはファイル名から拡張子と 01_ 形式の通し番号を落としたもの
.zip 中のテキストファイルを 1 つずつページにする。第 1 階層のフォルダ名がセクションになる(GET /api/notes/export の ZIP をそのまま戻せる)
  • 既存ページとタイトルが衝突したら タイトル (2) のように連番を足して別ページとして作る(上書きはしない)
  • 文字コードは UTF-8 (BOM 可) → CP932 の順に試す
  • 対象外の拡張子・NOTES_IMPORT_MAX_BYTES(既定 2,000,000)超のファイル・NOTES_IMPORT_MAX_PAGES(既定 100)を超えた分・壊れた ZIP は黙って飛ばす
  • 1 ページも取り込めなかった場合のみ 400

GET /api/notes/export

全ページを ZIP でダウンロードさせる(Content-Disposition: attachment)。 1 ページ 1 ファイルの Markdown で、本文はそのまま。エントリ名は <セクション>/<通し番号>_<タイトル>.md (セクション無しのページは ZIP 直下)。通し番号は position 順なのでタブの並びが残る。 ファイル名は notes_YYYYMMDD.zip。


GET /api/notes/{page_id}/export

1 ページを Markdown ファイルとしてダウンロードさせる。本文はそのままで、ファイル名がタイトルになる。 ページが存在しない場合は 404。

日本語タイトルを扱うため Content-Disposition には RFC 5987 形式を併記する:

attachment; filename="______.md"; filename*=UTF-8''%E8%B2%B7%E3%81%84%E7%89%A9%E3%83%AA%E3%82%B9%E3%83%88.md

ファイル名に使えない文字(\ / : * ? " < > | と制御文字)は _ に置き換え、80 文字で切る。


ローカルナレッジベース(閲覧)

backend/knowledge_base/**.md を GUI から読むための読み取り専用 API。編集・削除はできない。

GET /api/kb/files

ナレッジベース配下の Markdown を、パス順の一覧で返す(本文は含めない)。

[
  {
    "path": "web_search/02b627ac75bb2fd4.md",
    "name": "02b627ac75bb2fd4.md",
    "directory": "web_search",
    "title": "SOUL.md use-soul-with-hermes",
    "preview": "SOUL.mdは、Hermes Agent インスタンスの主要な…",
    "size": 6150,
    "updated_at": "2026-06-21T05:03:52.684324Z",
    "indexed": true
  }
]

directory はナレッジベース直下なら空文字。indexed は local_knowledge_docs に埋め込み済み (=話題に応じた自動注入の対象になっている)かどうか。未埋め込みのファイルは次回のチャット時に sync_local_kb() が取り込む。


GET /api/kb/file

1 ファイルの全文を返す。

クエリパラメーター

名前 必須 説明
path ✓ ナレッジベースディレクトリからの相対パス(web_search/xxxx.md など)

ナレッジベースの外を指すパス(../ や絶対パス)、.md 以外、存在しないファイルはすべて 404。

{
  "path": "fgo.md",
  "name": "fgo.md",
  "title": "Fate / Grand Order",
  "content": "# Fate / Grand Order

…",
  "size": 13942,
  "updated_at": "2026-06-20T09:39:45.542875Z"
}

GET /api/kb/embedding

埋め込みモデルの現状と、再埋め込みの進捗を返す。

{
  "model": "ollama/nomic-embed-text",
  "dimensions": 768,
  "file_count": 36,
  "indexed": 36,
  "stale": 0,
  "running": false,
  "total": 36,
  "done": 36,
  "failed": 0,
  "started_at": "2026-08-23T13:50:23.196274Z",
  "finished_at": "2026-08-23T13:50:29.951099Z",
  "last_error": null
}

indexed は現在のモデルで埋め込まれている件数、stale は別モデル(またはモデル不明)のまま残っている件数。 running 以下は実行中の進捗で、プロセス内のメモリにしか無い(再起動で消える。ワーカーを複数立てるとワーカーごとに別の値になる)。


POST /api/kb/embedding/test

短いテキストを実際に 1 回埋め込んでみて、モデルが応答するかを確かめる。失敗しても 200 で返し、error に理由が入る。

{ "ok": true, "model": "ollama/nomic-embed-text", "dimensions": 768, "latency_ms": 412.3, "error": null }

POST /api/kb/embedding/reindex

全ファイルの再埋め込みをバックグラウンドで開始し、開始直後の状態(running: true)を返す。 すでに実行中なら何も起こさず現在の進捗を返す。


ログ

GET /logs

LLM API 呼び出しログを created_at 降順で返す。

クエリパラメーター

パラメーター 型 説明
session_id string セッションで絞り込み
status ok / error 成功・失敗で絞り込み
limit int (≤500) 最大件数(デフォルト 100)
offset int スキップ件数

レスポンス

[
  {
    "id": 1,
    "session_id": "uuid",
    "model": "gpt-5.4",
    "request_json": "[{\"role\":\"user\",\"content\":\"...\"}]",
    "response_text": "...",
    "error": null,
    "latency_ms": 832.5,
    "created_at": "2026-06-01T12:00:00"
  }
]

DELETE /logs/{log_id}

指定ログを削除。204 No Content を返す。


ヘルスチェック

GET /health

{ "status": "ok" }