Skip to content

feat(support): AI-ассистент поддержки — FAQ (RAG) + тикеты через MCP - #18

Merged
Sermage merged 1 commit into
masterfrom
feat/support-assistant
Jul 19, 2026
Merged

feat(support): AI-ассистент поддержки — FAQ (RAG) + тикеты через MCP#18
Sermage merged 1 commit into
masterfrom
feat/support-assistant

Conversation

@Sermage

@Sermage Sermage commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Что это

Мини-сервис поддержки пользователей на базе jarvis-cli. Команда /support <вопрос> [#T-1024] отвечает на вопрос о продукте по FAQ (RAG) с учётом контекста тикета/пользователя (через MCP). Брат-близнец /help: FAQ вместо доков, тикеты вместо git-ветки.

Сценарий из задания «Почему не работает авторизация? #T-1024» работает end-to-end: агент сам поднимает тикет (тариф Free + вход через SSO ⇒ ошибка 403), читает FAQ и отвечает адресно.

Изменения по слоям

Слой Файл Роль
infra/ faq_retrieval.py MarkdownFaqRetrievalEngine — RAG по docs/support-faq/*.md, реализует порт RetrievalEngine, ноль зависимостей (взаимозаменяем с FAISS-движком)
infra/ ticket_store_client.py TicketStoreClient — in-process McpClient над JSON users/tickets (по образцу LocalFilesystemClient), тулы support__get_ticket · get_user · search_tickets
app/ support_assistant.py use case answer_support_question + порт SupportChat + PlainChatAdapter (деградация без tool calling)
cli/ support_commands.py, main.py, config.py, views.py /support, проводка в composition root, сидирование тикетов, справка
docs/ support-faq/{auth,billing,account}.md контент FAQ
examples/ support_agent_demo.py воспроизводимое демо без ключа (JARVIS_DEMO_SCRIPTED=1)
tests/ 3 файла, 31 тест стор тикетов, FAQ-движок, use case на фейках портов

Соответствие требованиям

  • ✅ отвечает на вопросы о продукте (/support)
  • ✅ RAG по FAQ (порт RetrievalEngine, заменяется на FAISS без правок use case)
  • ✅ учитывает контекст тикета — агент вызывает support__get_ticket/get_user в tool-loop
  • ✅ через MCP: JSON с пользователями/тикетами (TicketStoreClient стоит в McpRegistry; замена на реальную CRM = внешний MCP-сервер, код use case не меняется)

Проверка

```bash
JARVIS_DEMO_SCRIPTED=1 python3 examples/support_agent_demo.py # без ключа
python3 chat.py # затем: /support Почему не работает авторизация? #T-1024
pytest # 544 passed
```

Полный доступ к тикетам работает при provider=deepseek (tool calling); на других провайдерах /support деградирует до ответа только по FAQ.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WPCiJi7DVBdV2fjN2x3HMd

/support <вопрос> [#T-1024] — отвечает на вопрос о продукте по FAQ (RAG)
с учётом контекста тикета/пользователя (через MCP). Брат-близнец /help:
FAQ вместо доков, тикеты вместо git-ветки.

- infra/faq_retrieval.py::MarkdownFaqRetrievalEngine — RAG по docs/support-faq/*.md
  (реализует порт RetrievalEngine, лексический поиск, ноль зависимостей;
  взаимозаменяем с FAISS-движком).
- infra/ticket_store_client.py::TicketStoreClient — in-process McpClient над
  JSON users/tickets (по образцу LocalFilesystemClient), тулы
  support__get_ticket · get_user · search_tickets. Заменить на реальную CRM =
  поднять внешний MCP-сервер, use case не меняется.
- app/support_assistant.py::answer_support_question — оркеструет RetrievalEngine
  + SupportChat (ToolRouter или PlainChatAdapter-деградация).
- cli/support_commands.py + проводка в cli/main.py (сидирование тикетов,
  диспетчер, синхронизация при смене провайдера), справка, CLAUDE.md.
- Демо examples/support_agent_demo.py (JARVIS_DEMO_SCRIPTED=1, без ключа).
- Тесты: ticket store, FAQ-движок, use case на фейках (31 шт.).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPCiJi7DVBdV2fjN2x3HMd
@github-actions

Copy link
Copy Markdown

🤖 AI-ревью

🐞 Потенциальные баги

— замечаний нет

🏛 Архитектурные проблемы

  1. app/support_assistant.py, строка 9 — в docstring use case написано «Может использовать разные реализации портов», но класс PlainChatAdapter (строка 48) не реализует протокол SupportChatToolRouter возвращает ToolResult, а PlainChatAdapter возвращает _PlainResult. Формально это нарушение единого контракта.

  2. cli/main.py, строка 127_SAMPLE_TICKETS определён как глобальная константа в теле main(), что не соответствует архитектуре: данные должны храниться в domain или infra, а не в composition root.

  3. cli/main.py, строка 281TicketStoreClient создаётся внутри main() и регистрируется в McpRegistry, но McpRegistry по протоколу не имеет метода register() — в app/ports.py определён только clients(), get(), all_tools(), shutdown(). Либо McpRegistry должен расширить контракт, либо используется другой объект.

💡 Рекомендации

  1. app/support_assistant.pysupport_chat.chat() должен гарантированно возвращать объект с полями reply, trace, truncated. Рекомендуется либо сделать _PlainResult публичным, либо использовать общий dataclass для обоих вариантов.

  2. cli/main.py, строка 127 — перенести _SAMPLE_TICKETS и _seed_support_tickets в infra/ticket_store_client.py как константу и статический метод.

  3. cli/main.py, строка 281 — если McpRegistry не имеет метода register(), нужно либо добавить его в протокол app/ports.py, либо использовать конкретную реализацию StdioMcpRegistry напрямую.


Контекст (RAG): app/ports.py · cli/views.py — ── help ──────────────────────────────────────────────────────────────────── · cli/rag_commands.py · cli/main.py

Сгенерировано jarvis-cli · review_pr.py

@Sermage
Sermage merged commit fa4dc4f into master Jul 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant