Skip to content

Commit b2d7a7b

Browse files
committed
docs(PRD): status table, context-mode advisory, retrieval §5.8, NR backlog
Sync public knowledge PRD with shipped search (corpus, locale, MCP rate limit). Clarify context-mode is client-side; server may adopt BM25 ideas separately. Made-with: Cursor
1 parent c1139c4 commit b2d7a7b

1 file changed

Lines changed: 94 additions & 16 deletions

File tree

docs/PRD-public-knowledge-search-service.md

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
**Связь с MCP:** текущий поисковый sidecar — **основной upstream для инструментов знаний в `mcp-server`** (поиск по корпусу для MCP-клиентов). Любые изменения сервиса **`search`** (имя контейнера, URL, порты, формат вызова, переменные окружения, контракт ответа, health, аутентификация внутри сети при необходимости) **обязаны** быть отражены **в реализации MCP в том же репозитории `repo/api`**: код сервера MCP, конфигурация Docker Compose, деплой, тесты. Публичный REST под **`/knowledge/api`** и браузерный UI — **дополнительная** поверхность того же сервиса; **нельзя** менять только контейнер поиска без обновления путей вызова и контракта, которыми пользуется MCP. Подробно §5.3.
1616

17+
**Инструменты агента (например [context-mode](https://github.com/mksglu/context-mode)):** см. **§13** — это **не** замена сервису `search` на сервере, а дополнительный слой на машине разработчика/агента.
18+
1719
---
1820

1921
## 2. Goals
@@ -34,14 +36,19 @@
3436
- **Внутренняя реализация ответа** (как формируется текст ответа, откуда берётся контекст, какие компоненты задействованы) — **вне этого PRD** и **не отражается** во внешнем REST-контракте; клиентам это недоступно и не требуется.
3537
- Оплата/биллинг — вне scope; tier привязан к **валидности токена** и **конфигу**, не к Stripe.
3638
- Глобальный CDN — не требуется для v1.
39+
- **Встраивание клиентского MCP (в т.ч. context-mode) внутрь контейнера `search` как обязательной зависимости публичного API** — вне scope: см. **§13**.
3740

3841
---
3942

4043
## 4. Current state (baseline)
4144

42-
- Прод-стек: **Caddy** проксирует большую часть трафика на **`mcp-server:3000`**; sidecar поиска (сейчас **`qwen-search`**) слушает внутри сети (например `:8790`).
43-
- **MCP (`mcp-server`) напрямую зависит от этого сервиса:** инструменты knowledge вызывают поиск по HTTP (или контейнерному режиму) через конфигурацию вроде **`QWEN_MODE`**, **`QWEN_API_URL=http://qwen-search:8790`**, клиент в **`src/`** (qwen/MCP-логика), **`depends_on` / health** в **`docker-compose.prod.yml`**. Без работоспособного поискового sidecar цепочка MCP → знания **не выполняется**.
44-
- Токен **`API_TOKEN`** уже выдаётся оператором/ботом и кладётся в `.env` (для публичного API и других компонентов; для внутреннего вызова MCP из docker-сети правила могут отличаться — см. реализацию).
45+
*(по состоянию репозитория `repo/api`, дорожная карта — **§12**.)*
46+
47+
- Прод-стек: **Caddy** отдаёт **`/knowledge*`** на контейнер **`search`** (внутренний HTTP, порт **`8790`**); остальной трафик — на **`mcp-server:3000`** по существующим правилам.
48+
- **Сервис Compose:** имя **`search`**, образ **`spawndock/search:prod`**; каталог корпуса **`./knowledge:/corpus:ro`**; переменная **`KNOWLEDGE_ROOT=/corpus`** (см. `docker-compose.prod.yml`).
49+
- **MCP (`mcp-server`):** при **`SEARCH_REST_API=true`** вызывает **`SEARCH_API_URL`** (например `http://search:8790`) на **`POST /knowledge/api/v1/search`**; внутрисетевые запросы **без** `X-Forwarded-For` **не** попадают под публичный rate limit (**§5.4.6** реализовано).
50+
- **Пайплайн ответа:** локальное ранжирование фрагментов из корпуса (Markdown под `KNOWLEDGE_ROOT`), подстановка выдержек и директивы **`locale`** в промпт **Qwen Code CLI**; поля **`sources`** заполняются из ответа модели либо fallback по ранжированным файлам. Аутентификация Qwen — OAuth (`PROD_QWEN_OAUTH_CREDS` / `QWEN_OAUTH_CREDS_B64``~/.qwen/oauth_creds.json` в entrypoint).
51+
- Токен **`API_TOKEN`** выдаётся оператором/ботом и кладётся в `.env` на **`search`** и связанные сервисы; невалидный Bearer → **401**.
4552

4653
## 5. Target architecture
4754

@@ -256,27 +263,49 @@ rate_limit_tiers:
256263
- **Шифрование в транзите до пользователя** обеспечивается **Caddy** (и инфраструктурой перед ним), не отдельной настройкой `search`.
257264
- Рассмотреть **WAF-уровень** позже (на Caddy или выше — вне scope `search` v1).
258265

266+
### 5.8 Пайплайн извлечения контекста (maintainer-facing)
267+
268+
Раздел **не меняет** внешний REST-контракт, но фиксирует текущую реализацию и направления улучшений.
269+
270+
| Компонент | Статус | Заметки |
271+
|-----------|--------|---------|
272+
| Монтирование корпуса **`/corpus`** (прод) и **`KNOWLEDGE_ROOT`** | **Done** | Образ также содержит снимок **`knowledge/`** в **`/opt/search/knowledge`** для запуска без volume. |
273+
| Ранжирование фрагментов перед вызовом LLM | **Done** | Эвристика по токенам запроса и `.md` (Unicode-токены для кириллицы); см. `docker/search/knowledge-rank.mjs`, зеркало логики в `src/local-search.ts`. |
274+
| Учёт **`locale`** в промпте | **Done** | Явные инструкции `ru` / `en` / авто по языку запроса. |
275+
| Поле **`sources`** | **Done** | Из JSON ответа модели; если пусто — fallback из ранжированных источников. |
276+
| Диагностика сбоев Qwen (**stdout/stderr** в **502**) | **Done** | Усечённые потоки в `message` для оператора. |
277+
278+
**Будущие улучшения (см. §12, NR-RET):** по желанию заменить или дополнить эвристику поиском уровня **BM25 / FTS** (как в локальных MCP-инструментах), **RRF**, нормализация запросов — без изменения путей **`/knowledge/api/v1/*`**.
279+
259280
---
260281

261282
## 6. Configuration reference (для операторов)
262283

263-
Пример переменных (имена итоговые — утвердить в implementer pass):
284+
Актуальный набор (расширять по мере появления переменных в `docker-compose` / entrypoint):
264285

265-
- `SEARCH_HTTP_PORT`
266-
- `SEARCH_RATE_LIMIT_TIERS` (JSON) **или** путь к файлу `SEARCH_RATE_LIMIT_CONFIG`
267-
- `API_TOKEN` (shared secret для basic-tier в v1)
268-
- `TRUSTED_PROXY_CIDRS` / `REAL_IP_HEADER` для корректного IP клиента **за Caddy** (ingress уже терминирует TLS и проксирует HTTP на `search`)
286+
| Переменная | Назначение |
287+
|------------|------------|
288+
| `SEARCH_HTTP_PORT` / `QWEN_HTTP_PORT` | Порт HTTP listener (по умолчанию **8790**). |
289+
| `SEARCH_HTTP_BIND` / `QWEN_HTTP_BIND` | Bind address (по умолчанию **0.0.0.0**). |
290+
| `KNOWLEDGE_ROOT` | Корень Markdown-корпуса (**рекомендуется `/corpus`** в проде). |
291+
| `SEARCH_RATE_LIMIT_TIERS` | JSON override лимитов **free** / **basic** (см. §5.6). |
292+
| `API_TOKEN` | Общий секрет для **Bearer** и tier **basic** на **`search`**. |
293+
| `PROD_QWEN_OAUTH_CREDS` / `QWEN_OAUTH_CREDS_B64` | Base64 **oauth_creds** для Qwen CLI в контейнере; после смены секрета — **пересобрать/перезапустить** **`search`**. |
294+
| `QWEN_TIMEOUT_MS` / `SEARCH_TIMEOUT_MS` | Таймаут вызова Qwen. |
295+
| `X-Forwarded-For` | От Caddy — определяет публичный клиентский IP для rate limit; внутренние вызовы без этого заголовка лимит не потребляют. |
296+
297+
*Исторически:* часть имён сохраняет префикс **`QWEN_*`** у sidecar; новые параметры по возможности именовать **`SEARCH_*`**.
269298

270299
---
271300

272301
## 7. Migration plan
273302

274-
1. Ввести сервис **`search`** параллельно с `qwen-search` (опциональный short-lived alias в compose).
275-
2. **Обновить реализацию MCP** под новый hostname/URL/контракт: код (`src/mcp.ts`, `src/qwen/*` и смежное), переменные окружения **`mcp-server`**, интеграционные тесты; убедиться, что инструменты knowledge снова получают ответы от **`search`**.
276-
3. Переключить Caddy **`/knowledge*`****`search`** (UI + публичный API).
277-
4. Обновить ENV в **`mcp-server`** на новые имена; депрекейт **`QWEN_*`** после перевода.
278-
5. Удалить старый сервис **`qwen-search`** после проверки **и** регресса MCP.
279-
6. Обновить документацию оператора и Telegram-тексты при необходимости.
303+
1. ~~Ввести сервис **`search`** параллельно с `qwen-search`~~**Done** (отметка для истории; alias не обязателен).
304+
2. ~~**Обновить реализацию MCP** под новый hostname/URL/контракт~~**Done** (`SEARCH_REST_API`, `SEARCH_API_URL`, клиент `src/qwen/search-http.ts`).
305+
3. ~~Переключить Caddy **`/knowledge*`****`search`**~~**Done**.
306+
4. ~~Обновить ENV **`mcp-server`**; зачистить устаревшие **`QWEN_*`** где заменены~~**частично** (некоторые **`QWEN_*`** ещё используются у образа/search — см. compose; при рефакторинге свести к одной схеме имён).
307+
5. ~~Удалить **`qwen-search`**~~**Done** (as сервис **`search`**).
308+
6. Документация оператора / бот-тексты **по необходимости** (ссылка на этот PRD и OPERATOR.md).
280309

281310
---
282311

@@ -285,6 +314,8 @@ rate_limit_tiers:
285314
- Метрики: счётчики по tier (accepted, 429), latency search.
286315
- Алерты: рост доли 429 на free (возможная атака) vs. organic traffic.
287316

317+
**Статус:** в коде **`search`** пока нет встроенного Prometheus/OpenTelemetry; **NR-OBS** — вынести счётчики и гистограммы latency в следующую итерацию.
318+
288319
---
289320

290321
## 9. Testing requirements
@@ -294,12 +325,15 @@ rate_limit_tiers:
294325
- **MCP ↔ `search`:** тесты, подтверждающие, что **`mcp-server`** (или его модуль knowledge tool) получает корректный ответ от **`search`** при конфигурации, совпадающей с compose-деплоем.
295326
- Contract: snapshot OpenAPI + тесты **`/knowledge/api/v1/health`**, **`/knowledge/api/v1/search`**, плюс отдача UI и спецификации под **`/knowledge/`**.
296327

328+
**Сделано в репозитории:** оффлайн-проверка OpenAPI (`src/__tests__/knowledge-openapi-contract.test.ts`); **smoke** против прода/стенда: **`npm run smoke:knowledge`** (`scripts/smoke-knowledge-search.mjs`). E2E с поднятым Caddy в CI — **желательно** (NR-TEST).
329+
297330
---
298331

299332
## 10. Open questions
300333

301-
1. Дневной лимит: граница **UTC 00:00** vs. часовой пояс оператора (для v1 обычно UTC).
334+
1. Дневной лимит: граница **UTC 00:00** vs. часовой пояс оператора (для v1 обычно UTC)**зафиксировать в OPERATOR.md**.
302335
2. Нужны ли дополнительные read-only эндпоинты под **`/knowledge/api/v1/...`** (например метаданные корпуса) с правилом «без квоты» или «с квотой».
336+
3. Нужна ли явная **версия корпуса** / ETag в **`meta`** ответа **search** для клиентов SDK.
303337

304338
---
305339

@@ -310,4 +344,48 @@ rate_limit_tiers:
310344

311345
---
312346

313-
*Document version: 1.4 — 2026-03-24 — добавлена обязательная связь с реализацией **MCP** (§1, §4, §5.3, G7, миграция, тесты); уточнены внутренний vs публичный вход и rate limit для MCP (§5.4.6).*
347+
## 12. Implementation status (живая сводка)
348+
349+
### 12.1 Goals §2
350+
351+
| ID | Статус | Примечание |
352+
|----|--------|------------|
353+
| G1 | **Done** | Сервис **`search`**, OpenAPI **`/knowledge/api/v1/*`**. |
354+
| G2 | **Done** | Caddy → **`search`** для **`/knowledge*`**. |
355+
| G3 | **Done** | Swagger UI, **`openapi/knowledge-v1.yaml`**. |
356+
| G4 | **Done** | Опциональный Bearer, **401** при неверном токене. |
357+
| G5 | **Done** | Дефолтные лимиты; **429** с `tier` / `limit`. |
358+
| G6 | **Done** | `SEARCH_RATE_LIMIT_TIERS` (JSON). |
359+
| G7 | **Done** | MCP + compose + тесты синхронизированы. |
360+
361+
### 12.2 Новые требования (backlog)
362+
363+
| ID | Требование | Приоритет |
364+
|----|------------|-----------|
365+
| **NR-RET-1** | Оценить **FTS5 / BM25** или **сведение рангов (RRF)** для релевантности фрагментов корпуса вместо или вместе с текущей эвристикой; сохранить контракт API. | P2 |
366+
| **NR-RET-2** | Опциональный **кэш** ответов по `(query нормализованный, locale, версия корпуса)` при неизменном корпусе — снижение стоимости Qwen и latency. | P3 |
367+
| **NR-OBS-1** | Метрики (**accepted/429/latency/502**) и точки интеграции с мониторингом хоста. | P2 |
368+
| **NR-HA-1** | При **>1 реплики** `search` — вынести дневные/минутные счётчики rate limit из in-memory (**Redis** и аналоги); см. §5.6.3. | P2 |
369+
| **NR-TEST-1** | CI: e2e контейнер **`search`** + health + search с моком Qwen или dry-run режимом. | P3 |
370+
| **NR-DOC-1** | OpenAPI: уточнить схему **`sources`** / **`meta`** (согласованность `file` с историческими упоминаниями `path` / `title` в тексте PRD). | P3 |
371+
372+
---
373+
374+
## 13. Context-mode ([`mksglu/context-mode`](https://github.com/mksglu/context-mode)) и оптимизация
375+
376+
**Что это:** MCP-сервер и связанные хуки для **локальной** среды агента (Cursor, Claude Code и др.). Даёт **песочницу** для команд, **индексацию** (FTS5, BM25, RRF) и **поиск по индексу** так, чтобы **сырые объёмы** не попадали в контекстное окно модели.
377+
378+
**Связь с сервисом `search`:**
379+
380+
| Вопрос | Ответ |
381+
|--------|--------|
382+
| Можно ли «просто подключить» context-mode внутрь Docker **`search`** как замену Qwen? | **Нет.** context-mode не является HTTP-сервисом ответов на вопросы пользователей; это протокол **MCP** и инструменты для **клиента** агента. |
383+
| Дает ли context-mode выигрыш **на сервере** SpawnDock? | **Не напрямую.** Запуск MCP stdio в sidecar усложнит архитектуру и не даст публичному REST стабильного контракта без отдельного адаптера. Имеет смысл **заимствовать идеи** (BM25, RRF,/snippets) в **`knowledge-rank`** или отдельном модуле — см. **NR-RET-1** / §5.8. |
384+
| Где context-mode полезен продукту SpawnDock? | Для **разработчиков и операторов**: при работе с логами деплоя, длинной документацией и ответами **`/knowledge/api`** использовать **`ctx_execute` / `ctx_batch_execute` / `ctx_index`** так, чтобы агент не засорял контекст; при интеграции с публичным API — снимать крупные JSON через sandbox. |
385+
| Лицензия | У проекта context-mode лицензия **ELv2** — перед копипастой кода в **`repo/api`** нужна **правовая** проверка; безопаснее внедрять схожие алгоритмы самостоятельно или через зависимости с совместимой лицензией. |
386+
387+
**Итог:** context-mode **не** ставится обязательной зависимостью сервиса **`search`**; оптимизация **серверного** поиска идёт через **NR-*** и §5.8; context-mode рекомендуется как **дополнение на стороне клиента** при разработке и эксплуатации.
388+
389+
---
390+
391+
*Document version: 1.5 — 2026-03-25 — §4/§6/§7 актуализированы; §5.8 пайплайн; §12 статус и backlog; §13 context-mode; §9 smoke-тесты; NR для retrieval/observability/HA.*

0 commit comments

Comments
 (0)