Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52dfc0c349 | ||
|
|
cd38cbf19d |
@@ -137,7 +137,7 @@ LLM に渡すコンテキストは `app/core/memory_kb/memory.py` の `get_conte
|
|||||||
| `MEMORY_SUMMARIZE_THRESHOLD` | `20` | 長期メモリ化するメッセージ数の閾値 |
|
| `MEMORY_SUMMARIZE_THRESHOLD` | `20` | 長期メモリ化するメッセージ数の閾値 |
|
||||||
| `KNOWLEDGE_CONTEXT_LIMIT` | `15` | 蓄積知識をシステムプロンプトに注入する最大件数 |
|
| `KNOWLEDGE_CONTEXT_LIMIT` | `15` | 蓄積知識をシステムプロンプトに注入する最大件数 |
|
||||||
| `TAVILY_API_KEY` | — | Tavily Web 検索の API キー(未設定なら `web_search` は空を返す) |
|
| `TAVILY_API_KEY` | — | Tavily Web 検索の API キー(未設定なら `web_search` は空を返す) |
|
||||||
| `SEARCH_SUMMARY_MODEL` | — | Web 検索結果の要約に使うモデル(未設定なら `DEFAULT_MODEL`) |
|
| `SEARCH_SUMMARY_MODEL` | — | Web 検索結果の要約に使うモデル(未設定なら `DEFAULT_MODEL`。GUI 設定画面から実行時変更可) |
|
||||||
| `TAGGING_MODEL` | — | メッセージの自動タグ付けに使うモデル(未設定なら `DEFAULT_MODEL`) |
|
| `TAGGING_MODEL` | — | メッセージの自動タグ付けに使うモデル(未設定なら `DEFAULT_MODEL`) |
|
||||||
| `LOCAL_KB_DIR` | `knowledge_base` | ローカル Markdown ナレッジベースのディレクトリ |
|
| `LOCAL_KB_DIR` | `knowledge_base` | ローカル Markdown ナレッジベースのディレクトリ |
|
||||||
| `LOCAL_KB_EMBEDDING_MODEL` | `text-embedding-3-small` | ローカル KB の埋め込みモデル(GUI 設定画面から実行時変更可) |
|
| `LOCAL_KB_EMBEDDING_MODEL` | `text-embedding-3-small` | ローカル KB の埋め込みモデル(GUI 設定画面から実行時変更可) |
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ router = APIRouter(prefix="/api/settings", tags=["settings"], dependencies=[Depe
|
|||||||
class SettingEntry(BaseModel):
|
class SettingEntry(BaseModel):
|
||||||
key: str
|
key: str
|
||||||
is_set: bool
|
is_set: bool
|
||||||
masked: str | None
|
# 秘匿キーはマスク済みの値、それ以外は現在値そのもの。未設定なら None
|
||||||
|
display_value: str | None
|
||||||
|
# 入力欄をパスワード扱いにするか(APIキー等は True)
|
||||||
|
secret: bool
|
||||||
overridden: bool
|
overridden: bool
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ TAVILY_API_KEY: str | None = os.getenv("TAVILY_API_KEY")
|
|||||||
# 蓄積した知識をシステムプロンプトに注入する際の最大件数
|
# 蓄積した知識をシステムプロンプトに注入する際の最大件数
|
||||||
KNOWLEDGE_CONTEXT_LIMIT: int = int(os.getenv("KNOWLEDGE_CONTEXT_LIMIT", "15"))
|
KNOWLEDGE_CONTEXT_LIMIT: int = int(os.getenv("KNOWLEDGE_CONTEXT_LIMIT", "15"))
|
||||||
# Web検索結果を構造的な要約にまとめる際に使うモデル(未設定ならDEFAULT_MODELを使用)
|
# Web検索結果を構造的な要約にまとめる際に使うモデル(未設定ならDEFAULT_MODELを使用)
|
||||||
|
# GUI設定画面から実行時に変更できるため、実際の参照は memory_kb/knowledge._summary_model() が os.environ から行う。
|
||||||
SEARCH_SUMMARY_MODEL: str | None = os.getenv("SEARCH_SUMMARY_MODEL")
|
SEARCH_SUMMARY_MODEL: str | None = os.getenv("SEARCH_SUMMARY_MODEL")
|
||||||
|
|
||||||
# ローカルMarkdownナレッジベース(話題に応じて関連ファイルをシステムプロンプトに注入)
|
# ローカルMarkdownナレッジベース(話題に応じて関連ファイルをシステムプロンプトに注入)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@@ -10,7 +11,6 @@ from app.core import llm
|
|||||||
from app.core.config import (
|
from app.core.config import (
|
||||||
KNOWLEDGE_CONTEXT_LIMIT,
|
KNOWLEDGE_CONTEXT_LIMIT,
|
||||||
KNOWLEDGE_TOOL_CONTENT_LIMIT,
|
KNOWLEDGE_TOOL_CONTENT_LIMIT,
|
||||||
SEARCH_SUMMARY_MODEL,
|
|
||||||
)
|
)
|
||||||
from app.core.memory_kb import local_knowledge
|
from app.core.memory_kb import local_knowledge
|
||||||
from app.db.models import KnowledgeEntry, KnowledgeSource
|
from app.db.models import KnowledgeEntry, KnowledgeSource
|
||||||
@@ -37,6 +37,14 @@ _SUMMARY_PROMPT = (
|
|||||||
_HEADLINE_MARKERS = ("要約:", "要約:")
|
_HEADLINE_MARKERS = ("要約:", "要約:")
|
||||||
|
|
||||||
|
|
||||||
|
def _summary_model() -> str | None:
|
||||||
|
"""GUIの設定画面から実行時に変更できるよう、起動時定数ではなく都度 os.environ を見に行く。
|
||||||
|
|
||||||
|
未設定(空文字含む)なら None を返し、llm.complete() 側で DEFAULT_MODEL にフォールバックさせる。
|
||||||
|
"""
|
||||||
|
return os.environ.get("SEARCH_SUMMARY_MODEL") or None
|
||||||
|
|
||||||
|
|
||||||
def _split_headline(text: str) -> tuple[str, str]:
|
def _split_headline(text: str) -> tuple[str, str]:
|
||||||
"""LLMの出力を (一行要約, 本文) に分ける。1行目が想定の形式でなければ見出しは空で返す。"""
|
"""LLMの出力を (一行要約, 本文) に分ける。1行目が想定の形式でなければ見出しは空で返す。"""
|
||||||
lines = text.strip().splitlines()
|
lines = text.strip().splitlines()
|
||||||
@@ -87,7 +95,7 @@ async def summarize_results(query: str, results: list[dict], model: str | None =
|
|||||||
try:
|
try:
|
||||||
text = await llm.complete(
|
text = await llm.complete(
|
||||||
[{"role": "user", "content": prompt}],
|
[{"role": "user", "content": prompt}],
|
||||||
model=model or SEARCH_SUMMARY_MODEL,
|
model=model or _summary_model(),
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Failed to summarize search results for query=%r", query)
|
logger.exception("Failed to summarize search results for query=%r", query)
|
||||||
|
|||||||
@@ -16,8 +16,18 @@ CONFIGURABLE_KEYS = [
|
|||||||
"OLLAMA_API_BASE",
|
"OLLAMA_API_BASE",
|
||||||
"TAVILY_API_KEY",
|
"TAVILY_API_KEY",
|
||||||
"LOCAL_KB_EMBEDDING_MODEL",
|
"LOCAL_KB_EMBEDDING_MODEL",
|
||||||
|
"SEARCH_SUMMARY_MODEL",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# 秘匿情報ではないキー。値をマスクせずそのまま返し、GUIでも通常のテキスト入力として扱う。
|
||||||
|
# モデル名やURLは「今どれが効いているか」が見えないと設定しづらいため。
|
||||||
|
NON_SECRET_KEYS = {
|
||||||
|
"AWS_REGION_NAME",
|
||||||
|
"OLLAMA_API_BASE",
|
||||||
|
"LOCAL_KB_EMBEDDING_MODEL",
|
||||||
|
"SEARCH_SUMMARY_MODEL",
|
||||||
|
}
|
||||||
|
|
||||||
# .env由来の起動時の値のスナップショット。clear() で戻す先として使う。
|
# .env由来の起動時の値のスナップショット。clear() で戻す先として使う。
|
||||||
_env_defaults: dict[str, str | None] = {}
|
_env_defaults: dict[str, str | None] = {}
|
||||||
_snapshot_taken = False
|
_snapshot_taken = False
|
||||||
@@ -47,15 +57,18 @@ def load_into_env(db: DBSession) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def list_masked(db: DBSession) -> list[dict]:
|
def list_masked(db: DBSession) -> list[dict]:
|
||||||
|
"""GUI設定画面に出す一覧。秘匿キーの値はマスクし、そうでないキーは現在値をそのまま見せる。"""
|
||||||
_ensure_snapshot()
|
_ensure_snapshot()
|
||||||
saved = {row.key: row.value for row in db.query(AppSetting).all()}
|
saved = {row.key: row.value for row in db.query(AppSetting).all()}
|
||||||
result = []
|
result = []
|
||||||
for key in CONFIGURABLE_KEYS:
|
for key in CONFIGURABLE_KEYS:
|
||||||
current = os.environ.get(key)
|
current = os.environ.get(key)
|
||||||
|
secret = key not in NON_SECRET_KEYS
|
||||||
result.append({
|
result.append({
|
||||||
"key": key,
|
"key": key,
|
||||||
"is_set": bool(current),
|
"is_set": bool(current),
|
||||||
"masked": _mask(current) if current else None,
|
"display_value": (_mask(current) if secret else current) if current else None,
|
||||||
|
"secret": secret,
|
||||||
"overridden": key in saved,
|
"overridden": key in saved,
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
|
|||||||
+5
-3
@@ -94,7 +94,7 @@ Discord 連携は [discord-bot.md](discord-bot.md)、ノートの操作方法は
|
|||||||
## 5. Web 検索と知識の蓄積
|
## 5. Web 検索と知識の蓄積
|
||||||
|
|
||||||
- LLM が必要と判断したときだけ `web_search` ツールが走る(Tavily。`TAVILY_API_KEY` 未設定なら空振り)。
|
- LLM が必要と判断したときだけ `web_search` ツールが走る(Tavily。`TAVILY_API_KEY` 未設定なら空振り)。
|
||||||
- 結果は `SEARCH_SUMMARY_MODEL` で構造化要約にされ、**2 か所**に保存される。
|
- 結果は `SEARCH_SUMMARY_MODEL`(未設定なら `DEFAULT_MODEL`。設定画面から変更可)で構造化要約にされ、**2 か所**に保存される。
|
||||||
- `knowledge_entries` / `knowledge_sources` テーブル → 次回以降、層 1 として全セッションに効く
|
- `knowledge_entries` / `knowledge_sources` テーブル → 次回以降、層 1 として全セッションに効く
|
||||||
- `knowledge_base/web_search/*.md` → 層 3 の類似度検索の対象になる
|
- `knowledge_base/web_search/*.md` → 層 3 の類似度検索の対象になる
|
||||||
- `POST /chat/knowledge/regenerate` で、既存の知識エントリを出典 URL から取り直して要約し直せる。
|
- `POST /chat/knowledge/regenerate` で、既存の知識エントリを出典 URL から取り直して要約し直せる。
|
||||||
@@ -159,8 +159,10 @@ Discord 連携は [discord-bot.md](discord-bot.md)、ノートの操作方法は
|
|||||||
設定画面からラベル/モデル文字列を追加・編集・削除できる(重複は 400 で弾く)。
|
設定画面からラベル/モデル文字列を追加・編集・削除できる(重複は 400 で弾く)。
|
||||||
- 設定画面では API キー類を**再起動なしで**変更できる。
|
- 設定画面では API キー類を**再起動なしで**変更できる。
|
||||||
- 変更できるのは許可リストにあるキーだけ(`OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / AWS 系 /
|
- 変更できるのは許可リストにあるキーだけ(`OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / AWS 系 /
|
||||||
`OLLAMA_API_BASE` / `TAVILY_API_KEY` / `LOCAL_KB_EMBEDDING_MODEL`)
|
`OLLAMA_API_BASE` / `TAVILY_API_KEY` / `LOCAL_KB_EMBEDDING_MODEL` / `SEARCH_SUMMARY_MODEL`)
|
||||||
- 値は末尾 4 文字だけを見せるマスク表示。空欄で保存すると「変更なし」扱い
|
- API キー類は末尾 4 文字だけを見せるマスク表示。モデル名や URL(`settings_store.NON_SECRET_KEYS`)は
|
||||||
|
現在値をそのまま表示し、入力欄も伏せ字にしない(今どれが効いているか分からないと設定しづらいため)
|
||||||
|
- 空欄で保存すると「変更なし」扱い
|
||||||
- 保存すると `app_settings` テーブルに入り `os.environ` に反映される。削除すると `.env` 由来の起動時の値に戻る
|
- 保存すると `app_settings` テーブルに入り `os.environ` に反映される。削除すると `.env` 由来の起動時の値に戻る
|
||||||
|
|
||||||
## 15-16. ログビューアとシステムモニタ
|
## 15-16. ログビューアとシステムモニタ
|
||||||
|
|||||||
@@ -176,7 +176,10 @@ export async function logout(): Promise<void> {
|
|||||||
export interface SettingEntry {
|
export interface SettingEntry {
|
||||||
key: string
|
key: string
|
||||||
is_set: boolean
|
is_set: boolean
|
||||||
masked: string | null
|
/** 秘匿キーはマスク済みの値、それ以外は現在値そのもの。未設定なら null。 */
|
||||||
|
display_value: string | null
|
||||||
|
/** APIキーなど、入力欄をパスワード扱いにするキーか。 */
|
||||||
|
secret: boolean
|
||||||
overridden: boolean
|
overridden: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,11 +46,9 @@ const KEY_LABELS: Record<string, string> = {
|
|||||||
OLLAMA_API_BASE: 'Ollama API Base URL',
|
OLLAMA_API_BASE: 'Ollama API Base URL',
|
||||||
TAVILY_API_KEY: 'Tavily APIキー',
|
TAVILY_API_KEY: 'Tavily APIキー',
|
||||||
LOCAL_KB_EMBEDDING_MODEL: '埋め込みモデル(ローカルナレッジベース検索用)',
|
LOCAL_KB_EMBEDDING_MODEL: '埋め込みモデル(ローカルナレッジベース検索用)',
|
||||||
|
SEARCH_SUMMARY_MODEL: '要約モデル(Web検索結果のまとめ用)',
|
||||||
}
|
}
|
||||||
|
|
||||||
// 秘匿情報ではないため、マスクせず通常のテキスト入力として表示するキー
|
|
||||||
const PLAIN_TEXT_KEYS = new Set(['LOCAL_KB_EMBEDDING_MODEL', 'OLLAMA_API_BASE', 'AWS_REGION_NAME'])
|
|
||||||
|
|
||||||
async function submitPassword() {
|
async function submitPassword() {
|
||||||
const ok = await store.unlock(passwordInput.value)
|
const ok = await store.unlock(passwordInput.value)
|
||||||
if (ok) passwordInput.value = ''
|
if (ok) passwordInput.value = ''
|
||||||
@@ -106,8 +104,8 @@ onMounted(() => {
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<input
|
<input
|
||||||
v-model="drafts[entry.key]"
|
v-model="drafts[entry.key]"
|
||||||
:type="PLAIN_TEXT_KEYS.has(entry.key) ? 'text' : 'password'"
|
:type="entry.secret ? 'password' : 'text'"
|
||||||
:placeholder="entry.is_set ? `設定済み ${entry.masked}` : '未設定'"
|
:placeholder="entry.is_set ? `現在: ${entry.display_value}` : '未設定'"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user