@besales/mcp 0.1.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +272 -17
  2. package/dist/auth/connection-store.d.ts +58 -0
  3. package/dist/auth/connection-store.js +208 -0
  4. package/dist/auth/connection-store.js.map +1 -0
  5. package/dist/auth/oauth-client.d.ts +27 -2
  6. package/dist/auth/oauth-client.js +62 -11
  7. package/dist/auth/oauth-client.js.map +1 -1
  8. package/dist/auth/session-workspace.d.ts +2 -0
  9. package/dist/auth/session-workspace.js +20 -0
  10. package/dist/auth/session-workspace.js.map +1 -0
  11. package/dist/auth/token-storage.d.ts +19 -5
  12. package/dist/auth/token-storage.js +11 -6
  13. package/dist/auth/token-storage.js.map +1 -1
  14. package/dist/cli.d.ts +2 -7
  15. package/dist/cli.js +111 -33
  16. package/dist/cli.js.map +1 -1
  17. package/dist/http/api-client.d.ts +4 -13
  18. package/dist/http/api-client.js +18 -18
  19. package/dist/http/api-client.js.map +1 -1
  20. package/dist/index.d.ts +8 -6
  21. package/dist/index.js +3 -2
  22. package/dist/index.js.map +1 -1
  23. package/dist/instructions/server-instructions.d.ts +15 -0
  24. package/dist/instructions/server-instructions.js +243 -0
  25. package/dist/instructions/server-instructions.js.map +1 -0
  26. package/dist/package-metadata.js +7 -1
  27. package/dist/package-metadata.js.map +1 -1
  28. package/dist/resources/concepts/feedback-sheets.md +77 -0
  29. package/dist/resources/concepts/sandbox.md +13 -0
  30. package/dist/resources/concepts/workbook-classification.md +241 -0
  31. package/dist/resources/docs/agent-behavior.md +393 -0
  32. package/dist/resources/docs/crm-integration.md +535 -0
  33. package/dist/resources/docs/files-and-uploads.md +295 -0
  34. package/dist/resources/docs/knowledge-base.md +521 -0
  35. package/dist/resources/docs/pipeline-builder.md +221 -0
  36. package/dist/resources/docs/pipeline-settings-deep.md +221 -0
  37. package/dist/resources/docs/platforms.md +513 -0
  38. package/dist/resources/docs/prompt-anatomy.md +298 -0
  39. package/dist/resources/docs/prompt-principles.md +289 -0
  40. package/dist/resources/registry.js +34 -12
  41. package/dist/resources/registry.js.map +1 -1
  42. package/dist/resources/workflows/compare-models.md +46 -0
  43. package/dist/resources/workflows/connect-crm-from-scratch.md +89 -0
  44. package/dist/resources/workflows/connect-datasource-from-scratch.md +92 -0
  45. package/dist/resources/workflows/extract-from-document.md +36 -0
  46. package/dist/resources/workflows/iterate-with-sandbox.md +31 -0
  47. package/dist/resources/workflows/platform-setup-from-scratch.md +113 -0
  48. package/dist/resources/workflows/production-readiness-check.md +41 -0
  49. package/dist/schemas/mcp-tools.json +2636 -182
  50. package/dist/server.js +2 -0
  51. package/dist/server.js.map +1 -1
  52. package/dist/tools/definitions/agent-design.d.ts +215 -0
  53. package/dist/tools/definitions/agent-design.js +643 -0
  54. package/dist/tools/definitions/agent-design.js.map +1 -0
  55. package/dist/tools/definitions/crm-platform.d.ts +211 -0
  56. package/dist/tools/definitions/crm-platform.js +1070 -0
  57. package/dist/tools/definitions/crm-platform.js.map +1 -0
  58. package/dist/tools/definitions/datasource.d.ts +40 -0
  59. package/dist/tools/definitions/datasource.js +196 -0
  60. package/dist/tools/definitions/datasource.js.map +1 -0
  61. package/dist/tools/definitions/knowledge.d.ts +215 -0
  62. package/dist/tools/definitions/knowledge.js +782 -0
  63. package/dist/tools/definitions/knowledge.js.map +1 -0
  64. package/dist/tools/definitions/model-comparison.d.ts +25 -0
  65. package/dist/tools/definitions/model-comparison.js +101 -0
  66. package/dist/tools/definitions/model-comparison.js.map +1 -0
  67. package/dist/tools/definitions/platform-setup.d.ts +412 -0
  68. package/dist/tools/definitions/platform-setup.js +738 -0
  69. package/dist/tools/definitions/platform-setup.js.map +1 -0
  70. package/dist/tools/definitions/session.d.ts +11 -0
  71. package/dist/tools/definitions/session.js +86 -0
  72. package/dist/tools/definitions/session.js.map +1 -0
  73. package/dist/tools/definitions/shared.d.ts +742 -0
  74. package/dist/tools/definitions/shared.js +773 -0
  75. package/dist/tools/definitions/shared.js.map +1 -0
  76. package/dist/tools/definitions.d.ts +873 -88
  77. package/dist/tools/definitions.js +14 -856
  78. package/dist/tools/definitions.js.map +1 -1
  79. package/dist/tools/registry.d.ts +3 -1
  80. package/dist/tools/registry.js +90 -11
  81. package/dist/tools/registry.js.map +1 -1
  82. package/dist/tools/result.d.ts +1 -1
  83. package/dist/tools/result.js +12 -4
  84. package/dist/tools/result.js.map +1 -1
  85. package/dist/utils/logger.js +2 -1
  86. package/dist/utils/logger.js.map +1 -1
  87. package/docs/host-setup.md +34 -15
  88. package/package.json +2 -2
  89. package/scripts/install-claude-desktop.js +89 -11
  90. package/scripts/mock-api-server.js +1 -1
  91. package/scripts/mock-credentials.js +49 -6
  92. package/dist/types/api-contract.gen.d.ts +0 -6975
  93. package/dist/types/api-contract.gen.js +0 -6
  94. package/dist/types/api-contract.gen.js.map +0 -1
@@ -0,0 +1,521 @@
1
+ # 07. База знаний и RAG
2
+
3
+ > **Источник правды:** `ai-aniomaly/docs/ai-prompt-builder/07-knowledge-base.md` — копия для MCP doc-bridge (v1.5 Increment 6b). Синкать при изменении оригинала.
4
+ >
5
+ > ⚠️ **MCP-контекст:** это доменная справка, написанная для людей/админ-UI. В MCP-сессии ты выполняешь те же операции **через besales_* tools**, а не через UI (drag-n-drop, клики в админке). Где текст ниже говорит «клиент/админ делает X через UI» — это «что должно получиться»; механику бери из tool (напр. загрузка файла → `besales_file_upload_request`; документ в knowledge → `besales_knowledge_document_upload`; сайт → `besales_knowledge_website_add`) + из workflow-ресурсов. Внутренние ссылки `[NN-name.md]` — относительные пути исходной доки, через MCP НЕ резолвятся: ищи `besales://docs/<slug>` или concept-resource.
6
+
7
+ ## 1. Назначение
8
+
9
+ База знаний нужна агенту, чтобы отвечать **по реальной информации** компании-клиента: документы, FAQ, прайсы, статьи, инструкции. Animaly использует Pinecone (vector DB) + локальную БД для хранения. Агент получает релевантные фрагменты во время каждого запроса (RAG — Retrieval Augmented Generation).
10
+
11
+ ## 2. Архитектура
12
+
13
+ ```
14
+ Workspace
15
+ └── Namespace (cleanNamespace + scopeType)
16
+ ├── KnowledgeDocument[] → KnowledgeChunk[] (Pinecone: pineconeDoc)
17
+ ├── Qa[] → QaAnswerChunk[] (Pinecone: pineconeQa)
18
+ ├── WebsiteKnowledge[] (Pinecone: pineconeWeb)
19
+ └── KnowledgeTable[] (Pinecone: pineconeTable)
20
+
21
+ AgentChat
22
+ └── knowledgeNamespaces: string[] — массив cleanNamespace
23
+ ```
24
+
25
+ При каждом запросе агента система:
26
+ 1. Берёт `query` (сообщение пользователя или его переписанная версия из QUERY_REWRITE).
27
+ 2. Делает vector-поиск в каждом из подключённых namespaces (по типам данных).
28
+ 3. Объединяет результаты, отсекает по `ragMinScore`/`qaMinScore`, берёт top-K (`ragTopK`).
29
+ 4. Подкладывает в промпт как `{knowledgeContext}`.
30
+
31
+ Для QA после поиска может запускаться `RERANKER` (`AgentSpecialized.RERANKER`), чтобы выбрать лучший QA-кандидат.
32
+
33
+ ## 3. Namespace — JSON-схема
34
+
35
+ ```json
36
+ {
37
+ "id": "uuid",
38
+ "cleanNamespace": "main_qa",
39
+ "displayName": "Main Q&A",
40
+ "scopeType": "PLATFORM",
41
+ "workspaceId": "<workspaceId>",
42
+ "platformId": "<platformId>",
43
+
44
+ "hasQa": true,
45
+ "hasDocuments": true,
46
+ "hasWebsites": false,
47
+ "hasTables": false,
48
+
49
+ "accessMode": "AUTO_QUERY",
50
+
51
+ "pineconeQa": "qa_main_qa_<workspaceId>_<platformId>",
52
+ "pineconeDoc": "doc_main_qa_<workspaceId>_<platformId>",
53
+ "pineconeWeb": null,
54
+ "pineconeTable": null
55
+ }
56
+ ```
57
+
58
+ | Поле | Описание |
59
+ |---|---|
60
+ | `cleanNamespace` | Человеко-читаемое имя (например, `general`, `support`). Уникально в рамках платформы (или workspace для `WORKSPACE` scope). |
61
+ | `displayName` | Имя для UI. |
62
+ | `scopeType` | `GLOBAL` (системный) / `WORKSPACE` (общий для workspace) / `PLATFORM` (только для одной платформы). |
63
+ | `workspaceId`, `platformId` | Привязка по scope. |
64
+ | `hasQa`, `hasDocuments`, `hasWebsites`, `hasTables` | Флаги активных типов данных. |
65
+ | `accessMode` | `AUTO_QUERY` (поиск автоматически на каждом сообщении) или `TOOL_ONLY` (поиск только по явному вызову `read_namespaces`). |
66
+ | `pineconeQa/Doc/Web/Table` | Реальные имена namespace-ов в Pinecone (генерируются системой при создании). |
67
+
68
+ ### `WORKSPACE` scope: привязка к нескольким платформам
69
+
70
+ `Namespace` с `scopeType=WORKSPACE` может быть привязан к нескольким `Platform` через `PlatformNamespace`:
71
+
72
+ ```json
73
+ {
74
+ "id": "uuid",
75
+ "platformId": "<platformId>",
76
+ "namespaceId": "<namespaceId>"
77
+ }
78
+ ```
79
+
80
+ Так одну общую базу знаний (например, общий FAQ) можно подключить к 3 разным каналам.
81
+
82
+ ## 4. Назначение namespace агенту
83
+
84
+ Через `AgentChat.knowledgeNamespaces` (массив строк):
85
+
86
+ ```json
87
+ {
88
+ "name": "main",
89
+ "knowledgeNamespaces": ["main_qa", "main_doc", "general_faq"]
90
+ }
91
+ ```
92
+
93
+ Имена в массиве — это `Namespace.cleanNamespace`. Агент получит данные из всех типов (qa/doc/web/table), доступных в этих namespaces.
94
+
95
+ ## 5. KnowledgeDocument — документы
96
+
97
+ ```json
98
+ {
99
+ "id": "uuid",
100
+ "filename": "price-list-2026.pdf",
101
+ "filepath": "s3://animaly/knowledge/price-list-2026.pdf",
102
+ "namespace": "doc_main_qa_<wsId>_<platformId>",
103
+ "namespaceRecordId": "<Namespace.id>",
104
+ "platformId": "<platformId>",
105
+ "workspaceId": "<workspaceId>",
106
+
107
+ "splitMode": "recursive",
108
+ "separator": null,
109
+ "chunkSize": 1000,
110
+ "chunkOverlap": 200,
111
+ "maxChunkSize": 2000,
112
+
113
+ "embeddingModel": "text-embedding-3-small",
114
+ "storageType": "s3",
115
+ "status": "COMPLETED"
116
+ }
117
+ ```
118
+
119
+ | Поле | Описание |
120
+ |---|---|
121
+ | `splitMode` | Стратегия разбиения текста (`recursive`, `paragraph`, и т.п.). |
122
+ | `chunkSize`, `chunkOverlap`, `maxChunkSize` | Параметры разбиения. |
123
+ | `embeddingModel` | Модель эмбеддингов (по умолчанию OpenAI). |
124
+ | `status` | enum `DocumentStatus`: `LOADED` → `TEXT_EXTRACTED` → `SPLIT` → `EMBEDDINGS_GENERATED` → `VECTOR_DB_UPLOADED` → `COMPLETED`. |
125
+
126
+ Чанки хранятся в `KnowledgeChunk` (`text`, `embedding`, `chunkIndex`).
127
+
128
+ ### 5.1. Заголовок документа НЕ попадает в эмбеддинг (contextual retrieval)
129
+
130
+ Эмбеддится **только сырой текст чанка**. `filename`/заголовок документа хранится в метаданных Pinecone, но **в вектор не входит** — чанк находится поиском только по словам, которые есть в нём самом. Чанк «Модуль 6. Колени» не содержит названия курса/темы, поэтому по запросу «какой курс от боли в коленях» матчится слабее, а в смешанном namespace путается с похожими темами других продуктов.
131
+
132
+ Приёмы:
133
+ - **Контекстный заголовок внутри чанка.** Начинай каждый смысловой блок строкой-контекстом, которая попадёт в эмбеддинг: `[Курс ДБО — суставы, позвоночник, боли] Модуль 6. Колени…`. Так вектор кодирует и тему, и принадлежность; LLM тоже видит, к чему относится фрагмент.
134
+ - **Контроль границ через `split_override`.** Чтобы «один смысловой юнит = один чанк» (и заголовок не отклеился), грузи документ с `split_mode=separator` + явным `separator` между юнитами (+ `max_chunk_size` с запасом). Иначе recursive-резка по `chunkSize` разорвёт блок, и второй кусок останется без заголовка.
135
+
136
+ ## 6. Qa — пары «вопрос-ответ»
137
+
138
+ ```json
139
+ {
140
+ "id": "uuid",
141
+ "question": "Какая стоимость доставки по Москве?",
142
+ "answer": "Доставка по Москве — 500 руб. в пределах МКАД. ...",
143
+ "alias": "delivery_moscow",
144
+ "namespace": "qa_main_qa_<wsId>_<platformId>",
145
+ "namespaceRecordId": "<Namespace.id>",
146
+ "platformId": "<platformId>",
147
+ "workspaceId": "<workspaceId>",
148
+
149
+ "mode": "SIMPLE",
150
+ "callOperator": false,
151
+ "chunkDelaySeconds": 10,
152
+ "isCallQaOnly": false,
153
+
154
+ "embeddingModel": "text-embedding-3-small",
155
+ "status": "COMPLETED",
156
+
157
+ "answerChunks": [
158
+ { "orderIndex": 0, "text": "Доставка по Москве — 500 руб. ...", "buttons": [], "storedFileId": null },
159
+ { "orderIndex": 1, "text": null, "buttons": [], "storedFileId": "<StoredFile.id>" }
160
+ ]
161
+ }
162
+ ```
163
+
164
+ | Поле | Описание |
165
+ |---|---|
166
+ | `question`, `answer` | Текст. `answer` используется как контекст; реально в чат уходит `answerChunks[]`. |
167
+ | `alias` | Уникальный псевдоним в `(workspaceId)`. Используется для `call_qa(alias_name)`. Опциональный, но обязательный, если `isCallQaOnly=true`. |
168
+ | `mode` | `SIMPLE` (RAG + QA), `STRICT` (только QA, дословно), `ONCE_STRICT` (первый раз STRICT, потом как контекст). |
169
+ | `callOperator` | Если `true` — после QA автоматически вызывается оператор. |
170
+ | `chunkDelaySeconds` | Задержка между отправкой чанков ответа (если их несколько). |
171
+ | `isCallQaOnly` | Если `true` — QA не векторизуется, доступна только через `call_qa(alias)`. |
172
+
173
+ ### `QaAnswerChunk` — фрагменты ответа
174
+
175
+ ```json
176
+ {
177
+ "orderIndex": 0,
178
+ "text": "Текст чанка",
179
+ "buttons": [
180
+ { "name": "Подробнее", "url": "https://example.com/delivery" }
181
+ ],
182
+ "storedFileId": "<StoredFile.id>"
183
+ }
184
+ ```
185
+
186
+ Чанк может быть текстовым (`text`), медийным (`storedFileId`) или иметь кнопки (`buttons`). Несколько чанков отправляются последовательно с задержкой `chunkDelaySeconds`.
187
+
188
+ > ⚠️ **Ссылки — в текст, не в кнопки (по умолчанию).** URL клади в `text` чанка/ответа, а НЕ в `buttons`. В режиме `SIMPLE` агент перегенерирует ответ из текста контекста, и URL, лежащий только в `buttons`, клиенту не доходит (агент его «не видит» и отвечает «не вижу ссылку»). Кнопки (`buttons`) добавляй ТОЛЬКО если пользователь явно попросил кнопку. То же при `besales_knowledge_qa_create` / `besales_knowledge_qa_patch`.
189
+
190
+ ## 7. WebsiteKnowledge — скрапленные сайты
191
+
192
+ ```json
193
+ {
194
+ "id": "uuid",
195
+ "url": "https://example.com",
196
+ "domain": "example.com",
197
+ "title": "Главная",
198
+ "description": "...",
199
+ "namespace": "web_main_qa_<wsId>_<platformId>",
200
+ "namespaceRecordId": "<Namespace.id>",
201
+ "status": "COMPLETED",
202
+ "totalPages": 142,
203
+ "totalChunks": 1530,
204
+ "chunkSize": 1000,
205
+ "chunkOverlap": 200,
206
+ "embeddingModel": "text-embedding-3-small",
207
+ "lastCrawledAt": "2026-04-25T10:00:00Z"
208
+ }
209
+ ```
210
+
211
+ Краулинг управляется отдельным сервисом (BullMQ-job). AI-сервис обычно не создаёт WebsiteKnowledge напрямую — это делает админ через UI.
212
+
213
+ ## 8. KnowledgeTable — таблицы (Google Sheets / Notion / Airtable)
214
+
215
+ ```json
216
+ {
217
+ "id": "uuid",
218
+ "name": "Прайс на услуги",
219
+ "type": "GOOGLE_SHEETS",
220
+ "url": "https://docs.google.com/spreadsheets/d/...",
221
+ "access": "PUBLIC",
222
+ "namespace": "table_main_qa_<wsId>_<platformId>",
223
+ "namespaceRecordId": "<Namespace.id>",
224
+
225
+ "headerRowIndex": 0,
226
+ "useVectorIndex": true,
227
+ "vectorEmbedMaxChars": 1000,
228
+ "vectorTopK": 5,
229
+
230
+ "isActive": true,
231
+ "errorCount": 0,
232
+ "lastSuccessAt": "2026-04-30T08:00:00Z"
233
+ }
234
+ ```
235
+
236
+ | Поле | Описание |
237
+ |---|---|
238
+ | `type` | enum `KnowledgeSourceType`: `GOOGLE_SHEETS`, `NOTION`, `AIRTABLE`. |
239
+ | `access` | `PUBLIC` / `PRIVATE`. |
240
+ | `useVectorIndex` | Индексировать содержимое таблицы для vector-поиска. Если `false` — таблица читается «сырой» при каждом запросе (медленнее, но всегда актуально). |
241
+ | `vectorTopK` | Лимит результатов из таблицы. |
242
+
243
+ ## 9. Параметры RAG на уровне агента
244
+
245
+ В `AgentChat`:
246
+
247
+ | Поле | Дефолт | Описание |
248
+ |---|---|---|
249
+ | `ragTopK` | 3 | Сколько top-K результатов брать из vector-поиска документов. |
250
+ | `ragMinScore` | 0.5 | Минимальная релевантность RAG-результата (0.0–1.0). |
251
+ | `qaMinScore` | 0.45 | Минимальная релевантность QA-совпадения (широкий recall; точность даёт LLM-reranker). |
252
+ | `qaContextLimit` | 3 | Лимит QA-кандидатов для контекста (1–6). |
253
+
254
+ > ⚠️ Значения в таблице — дефолты колонок `AgentChat` (что получает новый агент), они и действуют в рантайме. В коде есть вторичные fallback-константы (`DEFAULT_RAG_TOP_K=5`, `DEFAULT_RAG_MIN_SCORE=0.4`), которые срабатывают только если значение агента не задано — не путай их с дефолтом агента.
255
+
256
+ ## 10. Поток обработки RAG
257
+
258
+ ```
259
+ Сообщение пользователя
260
+
261
+ QUERY_REWRITE (через AiUsageOperationKind=KNOWLEDGE_QUERY_REWRITE)
262
+ Опционально: LLM переписывает запрос для лучшего поиска (учитывая историю).
263
+
264
+ Vector-поиск по namespaces агента
265
+ Параллельно во всех типах: qa, doc, web, table.
266
+
267
+ Фильтрация по min-score
268
+ QA: qaMinScore (0.45)
269
+ Doc/Web/Table: ragMinScore (0.5)
270
+
271
+ Reranker (AgentSpecialized.RERANKER)
272
+ Опционально: для QA выбирает лучший из top-K (если включён).
273
+
274
+ Top-K результатов
275
+ ragTopK для doc/web, qaContextLimit для QA.
276
+
277
+ Формирование {knowledgeContext} в промпте
278
+ ```
279
+
280
+ **Приоритет QA над документами.** Поиск по типам идёт параллельно, но сборка ответа — QA-first: если найдена релевантная QA-пара (≥ `qaMinScore`, прошла reranker) — ответ строится в QA-режиме (QA + документный RAG как дополнение); если подходящей QA нет — чистый документный RAG (`llm-main.service.ts`, ветка «Если НЕ НАШЛИ QA → RAG»). Практический вывод: слишком «широкая» QA перехватывает запросы, которые ты хотел увести в документный RAG. Держи QA-вопросы конкретными (цена, программа, противопоказания), а свободные «симптомные» запросы оставляй документам.
281
+
282
+ ## 11. Inline-пример: namespace для интернет-магазина
283
+
284
+ ### Конфигурация
285
+
286
+ ```json
287
+ {
288
+ "namespace": {
289
+ "cleanNamespace": "shop_main",
290
+ "displayName": "Интернет-магазин",
291
+ "scopeType": "PLATFORM",
292
+ "platformId": "<platformId>",
293
+ "workspaceId": "<workspaceId>",
294
+ "hasQa": true,
295
+ "hasDocuments": true,
296
+ "hasWebsites": true,
297
+ "hasTables": true,
298
+ "accessMode": "AUTO_QUERY"
299
+ },
300
+ "qaSamples": [
301
+ {
302
+ "question": "Какая стоимость доставки?",
303
+ "answer": "По Москве — 500 руб. в пределах МКАД. ...",
304
+ "alias": "delivery",
305
+ "mode": "SIMPLE"
306
+ },
307
+ {
308
+ "question": "Как работает гарантия?",
309
+ "answer": "Гарантия 1 год на все товары. ...",
310
+ "alias": "warranty",
311
+ "mode": "SIMPLE"
312
+ }
313
+ ],
314
+ "documents": [
315
+ { "filename": "price-list-2026.pdf", "splitMode": "recursive", "chunkSize": 1000 }
316
+ ],
317
+ "websites": [
318
+ { "url": "https://shop.example.com", "chunkSize": 1000 }
319
+ ],
320
+ "tables": [
321
+ {
322
+ "name": "Каталог товаров",
323
+ "type": "GOOGLE_SHEETS",
324
+ "url": "https://docs.google.com/spreadsheets/d/...",
325
+ "useVectorIndex": true,
326
+ "vectorTopK": 5
327
+ }
328
+ ],
329
+ "agent": {
330
+ "name": "main",
331
+ "knowledgeNamespaces": ["shop_main"],
332
+ "ragTopK": 3,
333
+ "ragMinScore": 0.5,
334
+ "qaMinScore": 0.78
335
+ }
336
+ }
337
+ ```
338
+
339
+ ## 12. Inline-пример: TOOL_ONLY namespace для бронирования услуг
340
+
341
+ Иногда не нужно искать в базе знаний на каждом сообщении — только когда AI явно решил уточнить (например, услуги/расписание):
342
+
343
+ ```json
344
+ {
345
+ "namespace": {
346
+ "cleanNamespace": "services_catalog",
347
+ "scopeType": "PLATFORM",
348
+ "accessMode": "TOOL_ONLY",
349
+ "hasQa": false,
350
+ "hasTables": true
351
+ },
352
+ "agent": {
353
+ "knowledgeNamespaces": ["services_catalog"],
354
+ "tools": ["builtin-read_namespaces"]
355
+ }
356
+ }
357
+ ```
358
+
359
+ Агент получит инструмент `read_namespaces` и будет вызывать его осознанно.
360
+
361
+ ## 13. Граничные случаи и валидация
362
+
363
+ | Правило | Что AI-сервису помнить |
364
+ |---|---|
365
+ | `cleanNamespace` уникален в `(workspaceId, ?platformId)` | Зависит от `scopeType`. |
366
+ | Pinecone-имена (`pineconeQa/Doc/...`) генерируются системой | Не пытаться задавать вручную. |
367
+ | Документы загружаются асинхронно | `status` пройдёт через несколько стадий, прежде чем `COMPLETED`. До этого RAG может не работать. |
368
+ | QA с `isCallQaOnly=true` не векторизуется | Доступна только через `call_qa(alias)`. Хорошо для готовых медиа-ответов. |
369
+ | `mode=STRICT` отдаёт answer дословно | LLM не перефразирует. Подходит для legal/юридических текстов. |
370
+ | QA `alias` уникален в `(workspaceId)` | Не дублировать. |
371
+ | `ragTopK` слишком большой → шум | Реалистично 3–5. |
372
+ | `qaMinScore` — баланс recall/точности | Дефолт **0.45** = широкий recall, точность даёт LLM-reranker. Без reranker (мало QA) держи выше (0.6–0.8), иначе ложные срабатывания. |
373
+ | `RERANKER` агент опционален | Если в namespace мало QA — можно без него. Если QA много (200+) — желательно. |
374
+ | `accessMode=TOOL_ONLY` без инструмента `read_namespaces` | Бесполезно — агент не сможет искать. |
375
+ | WebsiteKnowledge — отдельный воркфлоу | Краулинг через очередь, AI-сервис обычно не задаёт его — даёт URL администратору. |
376
+ | `KnowledgeTable.useVectorIndex=false` | Каждый запрос ходит во внешнюю Google Sheets API — медленно. Включать только для маленьких таблиц. |
377
+ | Cross-platform namespace | Workspace-namespace + `PlatformNamespace` для каждой платформы, которая должна видеть. |
378
+
379
+ ## 14. Workbook ingestion — загрузка из xlsx/Google Sheets
380
+
381
+ Помимо обычной загрузки документов через UI (multipart upload), Animaly умеет автоматически препарировать **рабочие книги** (xlsx-файлы или Google Sheets с несколькими листами) в `KnowledgeDocument`/`Qa`/`KnowledgeTable` через workbook-adapter. Этот flow задумывался для AI-ассистента типа Claude Code: пользователь даёт ссылку на табличку, AI-ассистент классифицирует каждый лист и решает, что куда отправить.
382
+
383
+ ### 14.1. Модель `SourceWorkbook`
384
+
385
+ ```json
386
+ {
387
+ "id": "uuid",
388
+ "workspaceId": "<workspaceId>",
389
+ "kind": "XLSX_UPLOAD", // или "GOOGLE_SHEETS"
390
+ "storedFileId": "<StoredFile.id>", // для XLSX_UPLOAD
391
+ "googleSpreadsheetId": null, // для GOOGLE_SHEETS
392
+ "canonicalUrl": "https://docs.google.com/...",
393
+ "originalTitle": "Каталог 2026.xlsx",
394
+ "classification": [
395
+ {
396
+ "sheetTitle": "Prices",
397
+ "sheetGid": 0,
398
+ "detectedKind": "price_table",
399
+ "destination": "knowledge_table",
400
+ "mappedEntityId": "<KnowledgeTable.id>",
401
+ "notes": "Прайс на основные позиции — индексировать через vector"
402
+ },
403
+ {
404
+ "sheetTitle": "FAQ",
405
+ "sheetGid": 12345,
406
+ "detectedKind": "qa_pairs",
407
+ "destination": "qa",
408
+ "mappedEntityId": null,
409
+ "notes": "Q&A для импорта через besales_knowledge_qa_create"
410
+ }
411
+ ],
412
+ "importedBy": "<userId or null>", // null если impersonated MCP key
413
+ "importedBySource": "MCP_KEY", // "USER" / "MCP_KEY"
414
+ "importedAt": "2026-05-27T10:00:00Z",
415
+ "archivedAt": null // soft delete
416
+ }
417
+ ```
418
+
419
+ | Поле | Описание |
420
+ |---|---|
421
+ | `kind` | enum `SourceWorkbookKind`: `XLSX_UPLOAD` (multipart файл) или `GOOGLE_SHEETS` (по URL) |
422
+ | `classification` | Json-массив с per-sheet решениями AI-ассистента. Формат: `{sheetTitle, sheetGid, detectedKind, destination, mappedEntityId?, notes?}` |
423
+ | `importedBySource` | enum `ImportedBySource`: `USER` (через UI/auth user) или `MCP_KEY` (через impersonated MCP key — `importedBy` может быть `null`) |
424
+ | `archivedAt` | Soft delete. Записи остаются в БД для аудита |
425
+
426
+ ### 14.2. Новые поля `KnowledgeDocument`
427
+
428
+ ```json
429
+ {
430
+ "id": "uuid",
431
+ "filename": "FAQ_Prices.md",
432
+ "source": "AI_INGESTED", // или "USER_UPLOAD" (default)
433
+ "sourceWorkbookId": "<SourceWorkbook.id>", // null для USER_UPLOAD
434
+ "sourceSheetTitle": "Prices", // какой лист породил
435
+ ...
436
+ }
437
+ ```
438
+
439
+ | Поле | Тип | Дефолт | Описание |
440
+ |---|---|---|---|
441
+ | `source` | enum `DocumentSource` | `USER_UPLOAD` | `USER_UPLOAD` — обычный multipart upload через UI; `AI_INGESTED` — создан workbook-adapter'ом из листа `SourceWorkbook` |
442
+ | `sourceWorkbookId` | uuid? | `null` | FK на `SourceWorkbook.id` (`onDelete: SetNull`). Заполнено для `AI_INGESTED`-документов |
443
+ | `sourceSheetTitle` | string? | `null` | Имя листа, из которого извлечён документ. Partial unique index по `(workspaceId, sourceWorkbookId, sourceSheetTitle) WHERE source = 'AI_INGESTED'` — гарантирует идемпотентный re-ingest одного и того же листа |
444
+
445
+ ### 14.3. Flow workbook ingestion
446
+
447
+ ```
448
+ 1. UI/CLI/MCP: upload xlsx → /api/v2/files → storedFileId
449
+ (или: pass Google Sheets URL целиком)
450
+
451
+ 2. POST /api/v2/workspaces/:wsId/workbooks/inspect
452
+ { storedFileId? | googleSheetsUrl? }
453
+ → server читает структуру всех листов (sample max 50 rows + hyperlink stats)
454
+ → upsert SourceWorkbook (workspaceId, kind, …)
455
+
456
+ 3. AI-ассистент (Claude Code / GPT) локально классифицирует листы:
457
+ - price_table → knowledge_table
458
+ - qa_pairs → qa
459
+ - product_descriptions → knowledge_document
460
+ - amocrm_leads_export → POST /workbooks/:id/extract-amocrm-leads (вернёт массив leadId)
461
+ - dialogue_corpus → ICP analysis (см. spec icp)
462
+ - feedback_sheet → FeedbackSheet (см. 31-google-sheets-feedback.md)
463
+
464
+ 4. PATCH /workbooks/:id/classification { classification: [...] }
465
+ → server сохраняет решение в SourceWorkbook.classification
466
+
467
+ 5. Per-sheet actions (по решению AI):
468
+ - POST /workbooks/:id/knowledge-document
469
+ → создаёт KnowledgeDocument с source=AI_INGESTED + auto-detect splitMode
470
+ - besales_knowledge_qa_create (для qa-пар из листа)
471
+ - besales_knowledge_table_link (для таблиц)
472
+
473
+ 6. POST /workbooks/:id/archive — soft delete после полной обработки
474
+ ```
475
+
476
+ ### 14.4. Auto-detect splitMode при `POST /workbooks/:id/knowledge-document`
477
+
478
+ Workbook-adapter использует heuristic для выбора `splitMode` нового документа на основе структуры листа:
479
+
480
+ | Heuristic | splitMode выбирается |
481
+ |---|---|
482
+ | 2 колонки, первая короткая, вторая длинная | `qa` (Q&A pairs) |
483
+ | Заголовки строк `## ...` или `=== ...` | `heading` (по заголовкам) |
484
+ | Абзацы разделены пустыми строками | `paragraph` |
485
+ | Нумерованные пункты `1.`, `2.`, ... | `numbered` |
486
+ | Fallback (структура не распознана) | `fixed-size` (с дефолтным `chunkSize`) |
487
+
488
+ ### 14.5. MCP tools для workbook ingestion
489
+
490
+ | Tool | Endpoint | Назначение |
491
+ |---|---|---|
492
+ | `besales_workbook_inspect` | POST `/workbooks/inspect` | Загрузить структуру + создать SourceWorkbook |
493
+ | `besales_workbook_extract_amocrm_leads` | POST `/workbooks/:id/extract-amocrm-leads` | Извлечь все leadId (regex `.amocrm.ru/leads/` + `/api/v4/`) — hard cap 10000 строк |
494
+ | `besales_workbook_to_knowledge_document` | POST `/workbooks/:id/knowledge-document` | Создать KnowledgeDocument из листа |
495
+
496
+ ### 14.6. Ограничения
497
+
498
+ | Лимит | Значение |
499
+ |---|---|
500
+ | Sample строк при `inspect` | 50 |
501
+ | Hard cap строк при `extract-amocrm-leads` | 10 000 |
502
+ | Cross-tenant защита | `SourceWorkbook.workspaceId` фиксирован owner-ом; повторный ingest того же файла в другой workspace создаёт новую запись |
503
+ | Идемпотентность re-ingest | Partial unique index гарантирует, что повторный `to-knowledge-document` для того же `(workspaceId, sourceWorkbookId, sourceSheetTitle)` обновит существующий документ, а не создаст дубль |
504
+
505
+ ### 14.7. Когда AI-сервис использует workbook ingestion
506
+
507
+ - Клиент даёт большую табличку с прайсом, FAQ, описаниями товаров — AI-сервис не пытается распарсить вручную, а вызывает `inspect` → классифицирует → `to-knowledge-document`.
508
+ - Клиент даёт Google Sheets с выгрузкой лидов AmoCRM (например, для прогрева через ICP-анализ) — AI вызывает `extract-amocrm-leads`, затем `besales_icp_import_dialogues` с полученными leadId.
509
+ - Клиент даёт лист с testing feedback — AI вызывает `besales_feedback_sheet_link` (см. [`31-google-sheets-feedback.md`](31-google-sheets-feedback.md)).
510
+
511
+ ## 15. Связь с другими подсистемами
512
+
513
+ | Подсистема | Связь |
514
+ |---|---|
515
+ | **Агенты** ([`01-agents.md`](01-agents.md)) | `knowledgeNamespaces[]`, `ragTopK`, `ragMinScore`, `qaMinScore`. |
516
+ | **Промпт** ([`02-prompt-anatomy.md`](02-prompt-anatomy.md)) | `{knowledgeContext}`, `{knowledgeInstructions}`. |
517
+ | **Инструменты** ([`03-tools-catalog.md`](03-tools-catalog.md)) | `builtin-read_namespaces` для `TOOL_ONLY`, `builtin-call_qa` для QA-aliases. |
518
+ | **Триггеры** ([`04-triggers-and-actions.md`](04-triggers-and-actions.md)) | `READ_NAMESPACES` action — RAG-поиск через триггер. |
519
+ | **AgentSpecialized** ([`01-agents.md`](01-agents.md)) | `RERANKER` для QA, опционально. |
520
+ | **Файлы** ([`17-files-and-uploads.md`](17-files-and-uploads.md)) | `SourceWorkbook.storedFileId` указывает на `StoredFile` (xlsx upload). |
521
+ | **Google Sheets feedback** ([`31-google-sheets-feedback.md`](31-google-sheets-feedback.md)) | Workbook с тестировщиками может быть размечен как feedback-sheet для linker'а |