You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(search): drop locale from POST /v1/search body
Public API accepts only { query }. Prompt uses query language only.
Cache keys no longer include locale; remove meta.locale_requested.
Update OpenAPI, smoke script, PRD, contract test.
Made-with: Cursor
-**MCP:** set `SEARCH_REST_API=true` and `SEARCH_API_URL=http://search:8790` on `mcp-server`.
8
8
9
9
**Rate limits** apply only when `X-Forwarded-For` is set (traffic via Caddy). Direct calls from Docker (e.g. MCP) skip public quotas. Override tiers with JSON env `SEARCH_RATE_LIMIT_TIERS` (see `DEFAULT_TIERS` in `http-server.mjs`).
Copy file name to clipboardExpand all lines: docs/PRD-public-knowledge-search-service.md
+8-9Lines changed: 8 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -47,7 +47,7 @@
47
47
- Прод-стек: **Caddy** отдаёт **`/knowledge*`** на контейнер **`search`** (внутренний HTTP, порт **`8790`**); остальной трафик — на **`mcp-server:3000`** по существующим правилам.
48
48
-**Сервис Compose:** имя **`search`**, образ **`spawndock/search:prod`**; каталог корпуса **`./knowledge:/corpus:ro`**; переменная **`KNOWLEDGE_ROOT=/corpus`** (см. `docker-compose.prod.yml`).
49
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).
50
+
-**Пайплайн ответа:** локальное ранжирование фрагментов из корпуса (Markdown под `KNOWLEDGE_ROOT`), подстановка выдержек в промпт**Qwen Code CLI**; язык ответа — **по языку запроса** (отдельного поля `locale` в API **нет**). Поля **`sources`**— из JSON модели либо fallback по ранжированным файлам. Аутентификация Qwen — OAuth (`PROD_QWEN_OAUTH_CREDS` / `QWEN_OAUTH_CREDS_B64` → `~/.qwen/oauth_creds.json` в entrypoint).
51
51
- Токен **`API_TOKEN`** выдаётся оператором/ботом и кладётся в `.env` на **`search`** и связанные сервисы; невалидный Bearer → **401**.
52
52
53
53
## 5. Target architecture
@@ -155,13 +155,12 @@ Caddy по-прежнему монтирует весь продукт под п
155
155
156
156
| Поле | Тип | Обязательность | Описание |
157
157
|------|-----|----------------|----------|
158
-
|`query`| string |**да**| Текст запроса к базе знаний; **без** полей, меняющих способ формирования ответа «снаружи» |
159
-
|`locale`| string | нет | Предпочитаемая локаль ответа (например `en`, `ru`) — если сервис поддерживает; при отсутствии — поведение по умолчанию |
158
+
|`query`| string |**да**| Текст запроса к базе знаний |
160
159
161
160
Пример:
162
161
163
162
```json
164
-
{ "query": "How do I wire a Telegram Mini App webhook?", "locale": "en" }
163
+
{ "query": "How do I wire a Telegram Mini App webhook?" }
165
164
```
166
165
167
166
**Ответ 200 (JSON):**
@@ -170,7 +169,7 @@ Caddy по-прежнему монтирует весь продукт под п
170
169
|------|-----|----------|
171
170
|`answer`| string | Основной текст ответа |
172
171
|`sources`| array |**Опционально.** Унифицированный список источников из корпуса (например `{ "path": "guides/foo.md", "title": "..." }`) — структура задаётся в OpenAPI `components.schemas`|
173
-
|`meta`| object |**Опционально.** Служебные не секретные поля (например `locale_requested`, `cache_hit` при попадании в LRU-кэш **`search`**) |
172
+
|`meta`| object |**Опционально.** Служебные не секретные поля (например `cache_hit` при попадании в LRU-кэш **`search`**) |
174
173
175
174
Нормативные детали полей **`sources`** / **`meta`** — только в OpenAPI; клиент опирается на спецификацию, без знания внутренней реализации.
176
175
@@ -271,7 +270,7 @@ rate_limit_tiers:
271
270
|-----------|--------|---------|
272
271
| Монтирование корпуса **`/corpus`** (прод) и **`KNOWLEDGE_ROOT`**|**Done**| Образ также содержит снимок **`knowledge/`** в **`/opt/search/knowledge`** для запуска без volume. |
273
272
| Ранжирование фрагментов перед вызовом LLM |**Done**| По умолчанию **[MiniSearch](https://github.com/lucaong/minisearch)** (BM25-алгоритм) по секциям Markdown; при пустом hit — fallback на эвристику по токенам. `SEARCH_RANKER=legacy` — только эвристика. См. `docker/search/knowledge-rank.mjs`; зеркало эвристики в `src/local-search.ts`. |
274
-
|Учёт **`locale`** в промпте |**Done**|Явные инструкции `ru` / `en` / авто по языку запроса. |
273
+
|Язык ответа |**Done**|Только по тексту **`query`** (инструкция модели: совпадать с языком запроса); параметра **`locale`** нет. |
275
274
| Поле **`sources`**|**Done**| Из JSON ответа модели; если пусто — fallback из ранжированных источников. |
276
275
| Диагностика сбоев Qwen (**stdout/stderr** в **502**) |**Done**| Усечённые потоки в `message` для оператора. |
277
276
| Совпадение ранжирования MCP и **search**|**Done**| Оба пути: MiniSearch по секциям + legacy fallback; `SEARCH_RANKER=legacy` для отката. |
@@ -291,7 +290,7 @@ rate_limit_tiers:
291
290
|`SEARCH_HTTP_BIND` / `QWEN_HTTP_BIND`| Bind address (по умолчанию **0.0.0.0**). |
292
291
|`KNOWLEDGE_ROOT`| Корень Markdown-корпуса (**рекомендуется `/corpus`** в проде). |
293
292
|`SEARCH_RANKER`|`minisearch` (по умолчанию) или `legacy` — только эвристика по токенам (MCP `local-search` и sidecar). |
|`KNOWLEDGE_CACHE_REVISION`| Произвольная строка для инвалидации кэша без смены файлов на диске. |
296
295
|`SEARCH_RATE_LIMIT_TIERS`| JSON override лимитов **free** / **basic** (см. §5.6). |
297
296
|`API_TOKEN`| Общий секрет для **Bearer** и tier **basic** на **`search`**. |
@@ -368,7 +367,7 @@ rate_limit_tiers:
368
367
| ID | Требование | Приоритет |
369
368
|----|------------|-----------|
370
369
|**NR-RET-1**|~~Оценить BM25~~ — **частично done** (MiniSearch + секции). Далее: **FTS5 / RRF / trigram** при необходимости; контракт API без изменений. | P2 |
371
-
|**NR-RET-2**|**Частично done:** in-memory LRU в **`search`** (`SEARCH_RESPONSE_CACHE_MAX`, mtime корпуса + `KNOWLEDGE_CACHE_REVISION`). Далее: shared store при нескольких репликах. | P3 |
370
+
|**NR-RET-2**|**Частично done:** in-memory LRU в **`search`** (`SEARCH_RESPONSE_CACHE_MAX`, mtime корпуса + `KNOWLEDGE_CACHE_REVISION` + query). Далее: shared store при нескольких репликах. | P3 |
372
371
|**NR-OBS-1**| Метрики (**accepted/429/latency/502**) и точки интеграции с мониторингом хоста. | P2 |
373
372
|**NR-HA-1**| При **>1 реплики**`search` — вынести дневные/минутные счётчики rate limit из in-memory (**Redis** и аналоги); см. §5.6.3. | P2 |
374
373
|**NR-TEST-1**| CI: e2e контейнер **`search`** + health + search с моком Qwen или dry-run режимом. | P3 |
0 commit comments