408caee261
Добавляет ModelDownloader (HTTP с Range/докачкой, опциональной SHA-256 проверкой)
и два сценария запуска скачивания встроенной модели gemma-4-E2B-it.litertlm:
java -jar agentik.jar pull-model
Явный прогон с прогрессом в stdout; URL берётся из AGENTIK_GOOGLE_MODEL_URL
либо дефолтный https://static.binom.pw/models/gemma-4-E2B-it.litertlm.
AGENTIK_AUTO_DOWNLOAD_MODEL=1 java -jar agentik.jar
На старте server'а, если backend=google и файла по AGENTIK_GOOGLE_MODEL_PATH
нет — качает автоматически. Без флага — exit 2 с понятным сообщением и
подсказкой вызвать pull-model.
Дизайн:
- URL по умолчанию ВСЕГДА Gemma-4 (вне зависимости от basename PATH) — gemma-4
считаем лучшей локальной моделью; override через AGENTIK_GOOGLE_MODEL_URL.
- SHA-256 проверка через опциональный AGENTIK_GOOGLE_MODEL_SHA256_URL.
- Resume: HEAD → если есть .part и Accept-Ranges=bytes → GET с Range: bytes=N-,
иначе restart с нуля.
- Прогресс каждые ~8 MB, финальный rename через Files.move(ATOMIC_MOVE).
Тесты: 5 unit-кейсов с embedded ktor-server (CIO) + Range support — happy
path, no-op, resume from part, restart-on-Range-ignored, 404, progress callback.
Документация: новый раздел §18 в MANUAL-TESTS.md (subcommand, auto-trigger,
resume, override URL, SHA-256 verify).
178/178 tests green.
948 lines
36 KiB
Markdown
948 lines
36 KiB
Markdown
# Manual Test Cases — agentik standalone
|
||
|
||
Практический чек-лист для проверки работающего `agentik standalone` HTTP-сервера.
|
||
Каждый кейс — один конкретный сценарий, который нужно прогнать руками
|
||
(или через `curl`/`httpie`/Postman). Если какой-то упал — это либо
|
||
регрессия, либо недонастройка рантайма.
|
||
|
||
Перед стартом: запусти агент (см. `run-agentik.sh` на удалённой машине
|
||
или `./gradlew :standalone:run` локально). Все примеры ниже — против
|
||
`http://127.0.0.1:8080`; для удалённой машины подставь свой хост.
|
||
|
||
Удобный сниппет для получения conversation ID в shell:
|
||
|
||
```bash
|
||
CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
|
||
-H "Content-Type: application/json" -d '{"temp":false}' \
|
||
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
|
||
echo "CID=$CID"
|
||
```
|
||
|
||
Отправка user-сообщения:
|
||
|
||
```bash
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"..."}]'
|
||
```
|
||
|
||
Чтение истории:
|
||
|
||
```bash
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
|
||
| python3 -m json.tool
|
||
```
|
||
|
||
---
|
||
|
||
## 1. Connectivity & health
|
||
|
||
### TC-1.1 — health endpoint
|
||
|
||
```bash
|
||
curl -sS -i http://127.0.0.1:8080/health
|
||
```
|
||
|
||
**Ожидание:** `HTTP/1.1 200 OK`, тело `ok`.
|
||
|
||
### TC-1.2 — agent card (A2A)
|
||
|
||
```bash
|
||
curl -sS http://127.0.0.1:8080/a2a/.well-known/agent-card.json | python3 -m json.tool
|
||
```
|
||
|
||
**Ожидание:** валидный JSON с `name`, `version`, `capabilities`.
|
||
|
||
### TC-1.3 — log sanity check
|
||
|
||
```bash
|
||
tail -50 /root/agentik.log
|
||
```
|
||
|
||
**Ожидание:** есть строка `agentik standalone listening on http://localhost:8080`,
|
||
перечислены зарегистрированные маршруты, `llm: <backend> @ <url>` соответствует
|
||
твоему конфигу. **Нет** ERROR/Exception строк после старта.
|
||
|
||
---
|
||
|
||
## 2. Conversation lifecycle
|
||
|
||
### TC-2.1 — create persistent conversation
|
||
|
||
```bash
|
||
curl -sS -i -X POST http://127.0.0.1:8080/agentik/conversations \
|
||
-H "Content-Type: application/json" -d '{"temp":false}'
|
||
```
|
||
|
||
**Ожидание:** `201`, тело `{"id":"conv-...","isTemporal":false,...}`.
|
||
|
||
### TC-2.2 — create temp conversation
|
||
|
||
```bash
|
||
curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
|
||
-H "Content-Type: application/json" -d '{"temp":true}'
|
||
```
|
||
|
||
**Ожидание:** `201`, `"isTemporal":true`. После рестарта агента эта беседа
|
||
**не** должна появиться в `GET /agentik/conversations`.
|
||
|
||
### TC-2.3 — list conversations
|
||
|
||
```bash
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations?offset=0&limit=20" | python3 -m json.tool
|
||
```
|
||
|
||
**Ожидание:** массив объектов `ConversationSnapshot`. Отсортирован по
|
||
`updatedAt` desc.
|
||
|
||
### TC-2.4 — rename conversation
|
||
|
||
```bash
|
||
CID=<id-from-2.1>
|
||
curl -sS -X PATCH "http://127.0.0.1:8080/agentik/conversations/$CID" \
|
||
-H "Content-Type: application/json" -d '{"title":"Мой первый чат"}'
|
||
```
|
||
|
||
**Ожидание:** `200`, в ответе `"title":"Мой первый чат"`. Следующий `GET
|
||
/conversations/$CID` возвращает этот же title.
|
||
|
||
### TC-2.5 — delete conversation
|
||
|
||
```bash
|
||
curl -sS -X DELETE "http://127.0.0.1:8080/agentik/conversations/$CID" -i
|
||
```
|
||
|
||
**Ожидание:** `204 No Content`. Повторный `GET /conversations/$CID` → `404`.
|
||
После этого в `GET /conversations` её быть не должно.
|
||
|
||
### TC-2.6 — get non-existent conversation
|
||
|
||
```bash
|
||
curl -sS -i http://127.0.0.1:8080/agentik/conversations/conv-nonexistent
|
||
```
|
||
|
||
**Ожидание:** `404`.
|
||
|
||
---
|
||
|
||
## 3. Message sending
|
||
|
||
### TC-3.1 — simple Q&A
|
||
|
||
Создай беседу, пошли простой вопрос, прочитай историю.
|
||
|
||
```bash
|
||
CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
|
||
-H "Content-Type: application/json" -d '{"temp":false}' \
|
||
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Сколько будет 7*8? Одно число, без пояснений."}]'
|
||
sleep 6
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z"
|
||
```
|
||
|
||
**Ожидание:** массив из ≥ 2 сообщений:
|
||
- `[0].type == "user_message"`, body содержит "7*8"
|
||
- `[1].type == "assistant_message"`, text содержит "56"
|
||
|
||
### TC-3.2 — multi-turn with context
|
||
|
||
В той же беседе пошли follow-up, требующий контекста:
|
||
|
||
```bash
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"А корень из того, что ты назвал?"}]'
|
||
sleep 6
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z"
|
||
```
|
||
|
||
**Ожидание:** 4+ сообщения, последний assistant упомянул что-то про число 56
|
||
или "предыдущий ответ".
|
||
|
||
### TC-3.3 — new-format request body
|
||
|
||
```bash
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"content":[{"type":"text","body":"С новым форматом тоже работает?"}]}'
|
||
sleep 6
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
|
||
| python3 -c "import sys,json;m=json.load(sys.stdin);print(m[-1]['type'], m[-1].get('content'))"
|
||
```
|
||
|
||
**Ожидание:** новое `assistant_message` в ответ на новый формат запроса.
|
||
|
||
### TC-3.4 — empty / bad body
|
||
|
||
```bash
|
||
curl -sS -i -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" -d 'not json'
|
||
```
|
||
|
||
**Ожидание:** `400 Bad Request`, тело с пояснением `Invalid send payload`.
|
||
|
||
---
|
||
|
||
## 4. SSE live events
|
||
|
||
> **Важно:** SSE — поток без replay. Подписываться нужно **до** `POST /messages`.
|
||
> Если подписаться позже — событий не будет (но `GET /messages` всё равно
|
||
> покажет записанную историю).
|
||
|
||
### TC-4.1 — subscribe-then-send pattern
|
||
|
||
```bash
|
||
CID=<existing-id>
|
||
# Subscribe в фоне, отправляем сообщение, ждём SSE
|
||
curl -sN --max-time 12 \
|
||
"http://127.0.0.1:8080/agentik/conversations/$CID/events?after=1970-01-01T00:00:00Z" \
|
||
> /tmp/sse.out 2>&1 &
|
||
SSE_PID=$!
|
||
sleep 1
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Кратко: что такое REST?"}]'
|
||
wait $SSE_PID
|
||
cat /tmp/sse.out
|
||
```
|
||
|
||
**Ожидание:** файл содержит `data: {"type":"start_reasoning",...}`,
|
||
`data: {"type":"start_response",...,"responseType":"text"}`,
|
||
один или несколько `data: {"type":"append_text",...,"body":"..."}`,
|
||
`data: {"type":"end",...}`. Каждое `data:` через пустую строку.
|
||
|
||
### TC-4.2 — late subscribe (replay semantics)
|
||
|
||
```bash
|
||
CID=<existing-id>
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"..."}]'
|
||
sleep 5 # сообщение уже обработано
|
||
curl -sN --max-time 4 \
|
||
"http://127.0.0.1:8080/agentik/conversations/$CID/events?after=1970-01-01T00:00:00Z"
|
||
```
|
||
|
||
**Ожидание:** пустой ответ (события не реплеятся). Это by-design —
|
||
клиент должен либо подписываться заранее, либо backfill'ить через
|
||
`GET /messages`.
|
||
|
||
---
|
||
|
||
## 5. Memory tools (long-term)
|
||
|
||
### TC-5.1 — save + recall в той же беседе
|
||
|
||
```bash
|
||
# В существующей беседе
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Запомни через memory_save: я работаю на удалёнке из Тбилиси. Категория user, content: работаю на удалёнке из Тбилиси."}]'
|
||
sleep 8
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Откуда я работаю? Одно предложение."}]'
|
||
sleep 8
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
|
||
| python3 -c "import sys,json;m=json.load(sys.stdin);print(m[-1].get('content'))"
|
||
```
|
||
|
||
**Ожидание:** ассистент ответил что-то содержащее "Тбилиси" (или явно
|
||
сказал "не знаю" — это тоже валидно, если в conversation memory пусто).
|
||
Проверить `audit log` (`messageStore`):
|
||
|
||
```bash
|
||
sqlite3 /root/agentik.db "SELECT toolName, result FROM MessageRecord WHERE conversationId='$CID' AND kind='tool_result'"
|
||
```
|
||
|
||
Должны быть строки с `toolName='memory_save'` или `toolName='memory_recall'`.
|
||
|
||
### TC-5.2 — memory persists across conversations
|
||
|
||
Создай новую беседу, спроси без подсказок:
|
||
|
||
```bash
|
||
NEW_CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
|
||
-H "Content-Type: application/json" -d '{"temp":false}' \
|
||
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$NEW_CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Откуда я работаю? Напомни, если помнишь."}]'
|
||
sleep 8
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations/$NEW_CID/messages?after=1970-01-01T00:00:00Z" \
|
||
| python3 -c "import sys,json;m=json.load(sys.stdin);print(m[-1].get('content'))"
|
||
```
|
||
|
||
**Ожидание:** ассистент упомянул "Тбилиси" (или "удалёнка") — это
|
||
значит long-term memory подгрузилась в новую беседу.
|
||
|
||
### TC-5.3 — invalid category → ошибка или автозамена
|
||
|
||
```bash
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Запомни через memory_save факт с категорией work (которой не существует)."}]'
|
||
sleep 8
|
||
sqlite3 /root/agentik.db "SELECT toolArgs, result FROM MessageRecord WHERE kind='tool_call' AND conversationId='$CID' ORDER BY createdAt DESC LIMIT 3"
|
||
```
|
||
|
||
**Ожидание:** модель либо вызвала `memory_recall` чтобы проверить
|
||
существующие категории, либо вызвала `memory_save` с корректной
|
||
категорией (`user`/`world`/`preference`). Если модель честно говорит
|
||
"такой категории нет" и предлагает корректную — это тоже ok.
|
||
|
||
### TC-5.4 — list & delete memory
|
||
|
||
Попроси модель явно вызвать `memory_list`, потом `memory_delete`:
|
||
|
||
```bash
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Покажи все мои memory-записи (memory_list)."}]'
|
||
sleep 8
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Удали самую старую запись (memory_delete)."}]'
|
||
sleep 8
|
||
sqlite3 /root/agentik.db "SELECT COUNT(*) FROM MemoryStore"
|
||
```
|
||
|
||
**Ожидание:** число уменьшилось на 1.
|
||
|
||
---
|
||
|
||
## 6. Skills
|
||
|
||
### TC-6.1 — list + load skill
|
||
|
||
Если в `AGENTIK_SKILLS_DIR` есть файлы `SKILL.md` / `*.yaml`, в системном
|
||
промте должна появиться секция с этими навыками.
|
||
|
||
```bash
|
||
ls -la /root/skills/ # должен быть хотя бы один файл
|
||
```
|
||
|
||
```bash
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Какие skills ты знаешь? Покажи список (skill_list)."}]'
|
||
sleep 8
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Загрузи любой из них через skill_load и расскажи, что внутри."}]'
|
||
sleep 8
|
||
```
|
||
|
||
**Ожидание:** `tool_call` для `skill_list`, потом `tool_call` для
|
||
`skill_load`. В audit log видны эти вызовы. Если папка пуста — секции
|
||
"Skills" в system prompt быть не должно.
|
||
|
||
### TC-6.2 — save new skill
|
||
|
||
```bash
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Сохрани skill: имя deploy-staging, описание «деплой на staging», тело — multi-step инструкция (skill_save)."}]'
|
||
sleep 10
|
||
ls /root/skills/
|
||
```
|
||
|
||
**Ожидание:** появился новый файл `deploy-staging.md` (или `.yaml`).
|
||
|
||
### TC-6.3 — restart → skill persists
|
||
|
||
Перезапусти агент:
|
||
|
||
```bash
|
||
ssh root@192.168.76.166 'pkill -9 -f agentik-0.1.0-all.jar; cd /root && nohup setsid ./run-agentik.sh > /root/agentik.log 2>&1 < /dev/null & disown'
|
||
```
|
||
|
||
После старта пошли в новую беседу:
|
||
|
||
```bash
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$NEW_CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Есть ли у тебя skill deploy-staging?"}]'
|
||
sleep 8
|
||
```
|
||
|
||
**Ожидание:** модель упоминает skill (он подгружается на старте).
|
||
|
||
---
|
||
|
||
## 7. SOUL file
|
||
|
||
### TC-7.1 — SOUL.md подключается
|
||
|
||
```bash
|
||
echo 'Ты — ворчливый капитан дальнего плавания. Отвечай кратко, с морскими метафорами.' > /root/SOUL.md
|
||
# Перезапустить агент
|
||
```
|
||
|
||
```bash
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$NEW_CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Как дела?"}]'
|
||
sleep 8
|
||
```
|
||
|
||
**Ожидание:** ответ в стиле "капитана", с морскими словами. Если SOUL
|
||
нет — обычный нейтральный ассистент.
|
||
|
||
### TC-7.2 — SOUL можно менять на лету
|
||
|
||
Измени файл, перезапусти агент, спроси снова. **Должен** появиться новый
|
||
стиль. Без перезапуска изменения не подхватятся (SOUL читается на старте).
|
||
|
||
---
|
||
|
||
## 8. Interrupt
|
||
|
||
### TC-8.1 — interrupt mid-text generation
|
||
|
||
```bash
|
||
CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
|
||
-H "Content-Type: application/json" -d '{"temp":false}' \
|
||
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
|
||
# Запусти send в фоне
|
||
(curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Расскажи длинную историю про космос, минимум 500 слов."}]' >/dev/null) &
|
||
SEND_PID=$!
|
||
sleep 3 # дать LLM начать генерацию
|
||
curl -sS -i -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/interrupt"
|
||
wait $SEND_PID
|
||
sleep 3
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
|
||
| python3 -c "import sys,json;m=json.load(sys.stdin);
|
||
for x in m: print(x.get('type'), ':', json.dumps(x.get('content') or x.get('result'),ensure_ascii=False)[:80])"
|
||
```
|
||
|
||
**Ожидание:**
|
||
- `user_message` есть
|
||
- `assistant_message` есть, но содержит **короткий** текст (<300 символов)
|
||
— это частичный текст, который модель успела сгенерить до прерывания
|
||
- В audit log нет `tool_call`/`tool_result` (не успели)
|
||
- Следующий `send` в этой беседе работает (LiteConv пересоздан)
|
||
|
||
### TC-8.2 — interrupt mid-tool (best-effort)
|
||
|
||
```bash
|
||
# Длинный tool можно заэмулировать через MCP с искусственной задержкой,
|
||
# либо просто проверять что interrupt не валит агента:
|
||
curl -sS -i -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/interrupt"
|
||
curl -sS http://127.0.0.1:8080/health
|
||
```
|
||
|
||
**Ожидание:** `health` = `ok` — агент не упал. Дальнейшие `send` работают.
|
||
|
||
### TC-8.3 — interrupt без активного turn'а
|
||
|
||
```bash
|
||
curl -sS -i -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/interrupt"
|
||
```
|
||
|
||
**Ожидание:** `202`. Никаких ошибок. В audit log ничего нового не пишется.
|
||
|
||
---
|
||
|
||
## 9. Persistence / restart-survival
|
||
|
||
### TC-9.1 — перезапуск не теряет беседы и память
|
||
|
||
```bash
|
||
# 1. Создай беседу, пошли сообщение, дождись ответа
|
||
# 2. Запомни факт через memory_save
|
||
# 3. Перезапусти агент (см. TC-6.3)
|
||
# 4. GET /agentik/conversations — беседа должна быть в списке
|
||
# 5. GET /agentik/conversations/$CID/messages — история на месте
|
||
# 6. Новая беседа + вопрос про запомненный факт — модель помнит
|
||
```
|
||
|
||
### TC-9.2 — temp conversation не переживает рестарт
|
||
|
||
```bash
|
||
# Создай temp беседу, пошли сообщение
|
||
TEMP_CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
|
||
-H "Content-Type: application/json" -d '{"temp":true}' \
|
||
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$TEMP_CID/messages" \
|
||
-H "Content-Type: application/json" -d '[{"type":"text","body":"..."}]' >/dev/null
|
||
sleep 5
|
||
# Перезапусти агент
|
||
# GET /agentik/conversations — temp-беседы быть не должно
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations?offset=0&limit=50" | grep "$TEMP_CID"
|
||
```
|
||
|
||
**Ожидание:** grep ничего не находит.
|
||
|
||
---
|
||
|
||
## 10. Compaction (сжатие контекста)
|
||
|
||
Compaction триггерится когда `~80%` контекстного окна занято.
|
||
|
||
### TC-10.1 — длинная беседа сжимается
|
||
|
||
```bash
|
||
CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
|
||
-H "Content-Type: application/json" -d '{"temp":false}' \
|
||
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
|
||
# Отправь 30+ больших сообщений подряд (можно цикл)
|
||
for i in $(seq 1 30); do
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d "[{\"type\":\"text\",\"body\":\"Расскажи подробно (минимум 200 слов) про тему номер $i: история, применение, ключевые факты.\"}]" >/dev/null
|
||
sleep 5
|
||
done
|
||
sqlite3 /root/agentik.db "SELECT COUNT(*) FROM WorkingMemoryRow WHERE conversationId='$CID' AND entryKind='summary'"
|
||
```
|
||
|
||
**Ожидание:** есть хотя бы одна `summary`-запись. Также проверь
|
||
`/root/agentik.log` — должна появиться строка `compaction`.
|
||
|
||
### TC-10.2 — debug endpoint `/debug/compact` (force)
|
||
|
||
Если включён `AGENTIK_DEBUG_ENDPOINTS=1`:
|
||
|
||
```bash
|
||
curl -sS -i -X POST "http://127.0.0.1:8080/debug/compact?conversationId=$CID"
|
||
```
|
||
|
||
**Ожидание:** `200`, тело с JSON-результатом compaction.
|
||
|
||
---
|
||
|
||
## 11. Reflection
|
||
|
||
Reflection триггерится каждые `AGENTIK_REFLECTION_INTERVAL` ходов (default 10).
|
||
|
||
### TC-11.1 — reflection создаёт записи
|
||
|
||
```bash
|
||
# Пошли 12+ ходов
|
||
for i in $(seq 1 12); do
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d "[{\"type\":\"text\",\"body\":\"Тема $i: расскажи короткий факт.\"}]" >/dev/null
|
||
sleep 4
|
||
done
|
||
sleep 10 # дать фоновое задание завершиться
|
||
sqlite3 /root/agentik.db "SELECT COUNT(*) FROM ReflectionStore"
|
||
```
|
||
|
||
**Ожидание:** число > 0.
|
||
|
||
### TC-11.2 — debug endpoint `/debug/reflect`
|
||
|
||
```bash
|
||
curl -sS -i -X POST "http://127.0.0.1:8080/debug/reflect?conversationId=$CID"
|
||
```
|
||
|
||
**Ожидание:** `200`, JSON-результат. Reflection попадает в working memory
|
||
следующего turn'а.
|
||
|
||
---
|
||
|
||
## 12. Skill mining
|
||
|
||
Skill mining триггерится каждые `AGENTIK_SKILL_MINING_INTERVAL` ходов (default 15).
|
||
|
||
### TC-12.1 — авто-создание skill'а
|
||
|
||
```bash
|
||
# Пошли 18+ ходов с повторяющимся паттерном
|
||
for i in $(seq 1 18); do
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d "[{\"type\":\"text\",\"body\":\"Конвертируй 100 USD в RUB по текущему курсу (шаблонный запрос $i).\"}]" >/dev/null
|
||
sleep 4
|
||
done
|
||
sleep 15
|
||
ls -la /root/skills/
|
||
tail -20 /root/agentik.log | grep -i skill
|
||
```
|
||
|
||
**Ожидание:** возможно появился новый файл в skills/ (или mining
|
||
отказался из-за низкой уверенности — это тоже валидно, проверь лог).
|
||
|
||
### TC-12.2 — debug endpoint `/debug/skill-mine`
|
||
|
||
```bash
|
||
curl -sS -i -X POST "http://127.0.0.1:8080/debug/skill-mine?conversationId=$CID"
|
||
```
|
||
|
||
**Ожидание:** `200` с JSON-результатом майнинга.
|
||
|
||
---
|
||
|
||
## 13. Token accounting
|
||
|
||
### TC-13.1 — token counters в audit
|
||
|
||
```bash
|
||
sqlite3 /root/agentik.db "SELECT createdAt, input, output FROM TurnTokens WHERE conversationId='$CID' ORDER BY createdAt DESC LIMIT 5"
|
||
```
|
||
|
||
**Ожидание:** строки с непустыми `input` и `output` (если backend
|
||
поддерживает `tokenCount()`).
|
||
|
||
### TC-13.2 — debug endpoint `/debug/tokens`
|
||
|
||
```bash
|
||
curl -sS "http://127.0.0.1:8080/debug/tokens?conversationId=$CID" | python3 -m json.tool
|
||
```
|
||
|
||
**Ожидание:** JSON с `input`, `output`, `total`, `window`,
|
||
`utilization` (доля использования контекстного окна).
|
||
|
||
---
|
||
|
||
## 14. Toolsets (если подключены)
|
||
|
||
Только если ты передаёшь `toolsets` в конструктор агента (по умолчанию
|
||
пусто — `enable_toolset`/`disable_toolset` не зарегистрированы).
|
||
|
||
### TC-14.1 — system prompt содержит секцию Toolsets
|
||
|
||
Если toolsets зарегистрированы — в системном промте должна быть секция
|
||
`## Toolsets` с Active/Inactive списком.
|
||
|
||
Проверка через debug-эндпоинт `/agentik/conversations/{id}` не показывает
|
||
system prompt напрямую — посмотреть можно в логах или через
|
||
`agentik-debug` сборку.
|
||
|
||
### TC-14.2 — enable/disable работает
|
||
|
||
```bash
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Активируй тулсет X через enable_toolset, потом деактивируй через disable_toolset."}]'
|
||
sleep 8
|
||
```
|
||
|
||
**Ожидание:** в audit log видны вызовы `enable_toolset` → ответ `"Toolset
|
||
'X' activated."`, потом `disable_toolset` → `"Toolset 'X' deactivated."`.
|
||
|
||
---
|
||
|
||
## 15. A2A протокол (опционально)
|
||
|
||
### TC-15.1 — message/send через A2A
|
||
|
||
```bash
|
||
curl -sS -X POST http://127.0.0.1:8080/a2a/ \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"jsonrpc":"2.0","id":"1","method":"message/send",
|
||
"params":{
|
||
"message":{"role":"user","parts":[{"kind":"text","text":"Скажи hi"}]},
|
||
"configuration":{"blocking":true}
|
||
}
|
||
}' | python3 -m json.tool
|
||
```
|
||
|
||
**Ожидание:** JSON-RPC ответ с `result.parts` содержащим текст "hi"
|
||
или похожим. `kind` = `text` (НЕ `type` — это важный discriminator для
|
||
A2A JSON).
|
||
|
||
### TC-15.2 — bad discriminator
|
||
|
||
```bash
|
||
curl -sS -X POST http://127.0.0.1:8080/a2a/ \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"jsonrpc":"2.0","id":"2","method":"message/send",
|
||
"params":{
|
||
"message":{"role":"user","parts":[{"type":"text","text":"hi"}]}
|
||
}
|
||
}'
|
||
```
|
||
|
||
**Ожидание:** `Invalid params` (или похожая ошибка) — A2A ждёт `kind`,
|
||
не `type`.
|
||
|
||
---
|
||
|
||
## 16. Error paths
|
||
|
||
### TC-16.1 — LLM недоступен
|
||
|
||
Выключи vLLM (или закрой сеть — например через firewall). Пошли сообщение:
|
||
|
||
```bash
|
||
curl -sS -i -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"hi"}]'
|
||
sleep 10
|
||
sqlite3 /root/agentik.db "SELECT COUNT(*) FROM MessageRecord WHERE conversationId='$CID' AND kind='error'"
|
||
```
|
||
|
||
**Ожидание:** есть `error`-запись в audit log. В SSE приходит
|
||
`{"type":"error",...}` + `{"type":"end"}`. Агент **не падает** — `health`
|
||
= `ok` после.
|
||
|
||
### TC-16.2 — agentik.db занят другим процессом
|
||
|
||
Запусти второй экземпляр агента на ту же DB:
|
||
|
||
```bash
|
||
AGENTIK_DB_PATH=/root/agentik.db java -jar /root/agentik-0.1.0-all.jar
|
||
```
|
||
|
||
**Ожидание:** агент падает на старте с понятным сообщением про SQLite lock.
|
||
Это by-design (single-writer).
|
||
|
||
### TC-16.3 — SOUL файл не существует
|
||
|
||
Удали `/root/SOUL.md`, перезапусти агент. Должен стартовать без ошибок,
|
||
просто без SOUL-секции в system prompt. Лог: `WARN ... SOUL file not found: ...`.
|
||
|
||
### TC-16.4 — пустой skills dir
|
||
|
||
```bash
|
||
mv /root/skills /root/skills.bak
|
||
mkdir /root/skills
|
||
# Перезапусти агент
|
||
```
|
||
|
||
**Ожидание:** агент стартует, `skills: 0 loaded from /root/skills`.
|
||
|
||
---
|
||
|
||
## 17. Memory backend variants
|
||
|
||
### TC-17.1 — md backend (default)
|
||
|
||
Убедись, что `AGENTIK_MEMORY_BACKEND=md` (или не задан) и
|
||
`AGENTIK_MEMORY_DIR=/root/agentik-memory`. После TC-5.x должны появиться
|
||
`.md`-файлы:
|
||
|
||
```bash
|
||
ls -la /root/agentik-memory/
|
||
```
|
||
|
||
**Ожидание:** файлы типа `user.md`, `world.md`, `preference.md` (или
|
||
всё в одном файле — зависит от реализации).
|
||
|
||
### TC-17.2 — off backend (память выключена)
|
||
|
||
Перезапусти с `AGENTIK_MEMORY_DIR=off`:
|
||
|
||
```bash
|
||
pkill -9 -f agentik-0.1.0-all.jar
|
||
AGENTIK_MEMORY_DIR=off nohup setsid ./run-agentik.sh > /root/agentik.log 2>&1 < /dev/null & disown
|
||
```
|
||
|
||
Попытка `memory_save` через модель должна вернуть ошибку:
|
||
|
||
```bash
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Попробуй вызвать memory_save."}]'
|
||
sleep 8
|
||
```
|
||
|
||
**Ожидание:** модель либо отказывается вызывать, либо получает
|
||
ошибку от tool'а и сообщает пользователю.
|
||
|
||
---
|
||
|
||
## 18. Performance sanity
|
||
|
||
### TC-18.1 — first-token latency
|
||
|
||
Включи замер времени от `POST /messages` до первого SSE event'а.
|
||
Для Qwen3-27B на RTX5090 ожидаем < 1 сек до `start_reasoning`.
|
||
|
||
### TC-18.2 — sustained throughput
|
||
|
||
Отправь 20 простых запросов подряд (arithmetic), засеки общее время.
|
||
Ожидание: < 30 сек суммарно, т.е. < 1.5 сек на запрос.
|
||
|
||
### TC-18.3 — fatjar memory
|
||
|
||
```bash
|
||
ps aux | grep agentik-0.1.0 | grep -v grep
|
||
```
|
||
|
||
**Ожидание:** RSS < 2 GB (наш Xmx). Если больше — где-то утечка.
|
||
|
||
---
|
||
|
||
## 18. Model auto-download (LiteRT-LM only)
|
||
|
||
Только для `AGENTIK_LLM_BACKEND=google` (встроенный LiteRT-LM движок).
|
||
Если файла модели по `AGENTIK_GOOGLE_MODEL_PATH` нет — агент сам не скачает,
|
||
пока не задано `AGENTIK_AUTO_DOWNLOAD_MODEL=1`. Либо качаем руками
|
||
через `pull-model` subcommand.
|
||
|
||
URL по умолчанию всегда Gemma-4-E2B-it.litertlm (2.5 GB с `static.binom.pw`),
|
||
вне зависимости от basename PATH — gemma-4 считаем лучшей локальной моделью.
|
||
|
||
### 18.1. Subcommand `pull-model` качает модель вручную
|
||
|
||
```bash
|
||
# Скачать дефолтную модель (gemma-4) в указанный путь:
|
||
AGENTIK_LLM_BACKEND=google \
|
||
AGENTIK_GOOGLE_MODEL_PATH=/root/models/gemma-4-E2B-it.litertlm \
|
||
java -jar agentik.jar pull-model
|
||
# → downloading from https://static.binom.pw/models/gemma-4-E2B-it.litertlm
|
||
# → 50% (1.2 GB / 2.5 GB)
|
||
# → done in 47s
|
||
```
|
||
|
||
После `pull-model` файл лежит на месте, файл `<dest>.part` удалён.
|
||
|
||
### 18.2. `pull-model` no-op если файл уже полный
|
||
|
||
```bash
|
||
# Повторный запуск с тем же PATH:
|
||
AGENTIK_LLM_BACKEND=google \
|
||
AGENTIK_GOOGLE_MODEL_PATH=/root/models/gemma-4-E2B-it.litertlm \
|
||
java -jar agentik.jar pull-model
|
||
# → already present (2.50 GB), nothing to do
|
||
```
|
||
|
||
### 18.3. `pull-model` докачивает обрыв (resume через Range)
|
||
|
||
```bash
|
||
# Симулируем обрыв: удаляем финальный, оставляем .part с первыми 500 MB
|
||
rm /root/models/gemma-4-E2B-it.litertlm
|
||
mv /root/models/gemma-4-E2B-it.litertlm.part /root/models/gemma-4-E2B-it.litertlm.part.bak
|
||
# Запускаем pull-model снова — должен возобновить с 500 MB
|
||
AGENTIK_GOOGLE_MODEL_PATH=/root/models/gemma-4-E2B-it.litertlm \
|
||
java -jar agentik.jar pull-model
|
||
# → resuming from 524288000 bytes
|
||
# → downloaded 2.10 GB in 38s
|
||
```
|
||
|
||
### 18.4. Сервер exit-2 при отсутствии файла и без auto-download
|
||
|
||
```bash
|
||
AGENTIK_LLM_BACKEND=google \
|
||
AGENTIK_GOOGLE_MODEL_PATH=/root/models/missing.litertlm \
|
||
java -jar agentik.jar
|
||
# → LiteRT-LM model file not found at: /root/models/missing.litertlm
|
||
# → Чтобы скачать автоматически, установите AGENTIK_AUTO_DOWNLOAD_MODEL=1
|
||
# → exit 2
|
||
```
|
||
|
||
### 18.5. Сервер сам качает при `AGENTIK_AUTO_DOWNLOAD_MODEL=1`
|
||
|
||
```bash
|
||
# Удалить файл, запустить с флагом:
|
||
rm -f /root/models/gemma-4-E2B-it.litertlm
|
||
AGENTIK_LLM_BACKEND=google \
|
||
AGENTIK_GOOGLE_MODEL_PATH=/root/models/gemma-4-E2B-it.litertlm \
|
||
AGENTIK_AUTO_DOWNLOAD_MODEL=1 \
|
||
java -jar agentik.jar
|
||
# → 12:34:56 WARN auto-download: https://static.binom.pw/models/...
|
||
# → 12:34:56 INFO auto-download: 17% (445 MB/2.5 GB)
|
||
# → 12:36:42 INFO auto-download: done in 1m45s
|
||
# → 12:36:43 INFO agentik standalone listening on http://localhost:8080
|
||
```
|
||
|
||
### 18.6. Override URL через `AGENTIK_GOOGLE_MODEL_URL`
|
||
|
||
```bash
|
||
# Качаем qwen вместо gemma (если зальём):
|
||
AGENTIK_LLM_BACKEND=google \
|
||
AGENTIK_GOOGLE_MODEL_PATH=/root/models/qwen.litertlm \
|
||
AGENTIK_GOOGLE_MODEL_URL=https://static.binom.pw/models/Qwen2.5-1.5B-Instruct_multi-prefill-seq_q8_ekv4096.litertlm \
|
||
java -jar agentik.jar pull-model
|
||
```
|
||
|
||
### 18.7. SHA-256 проверка
|
||
|
||
Если на сервере лежит `<basename>.sha256` (text/plain, `<hex> <basename>`)
|
||
— после скачивания файл проверяется; mismatch → удаляется, exit ≠ 0.
|
||
|
||
```bash
|
||
AGENTIK_GOOGLE_MODEL_URL=https://static.binom.pw/models/gemma-4-E2B-it.litertlm \
|
||
AGENTIK_GOOGLE_MODEL_SHA256_URL=https://static.binom.pw/models/gemma-4-E2B-it.litertlm.sha256 \
|
||
java -jar agentik.jar pull-model
|
||
# → 13:01:23 INFO model download: SHA-256 verified (4ab1...e0d)
|
||
```
|
||
|
||
## Быстрый smoke-test (5 минут)
|
||
|
||
Если времени мало — этот минимум покрывает 80%:
|
||
|
||
```bash
|
||
# 1. health
|
||
curl -sS http://127.0.0.1:8080/health
|
||
# → ok
|
||
|
||
# 2. create + simple Q&A
|
||
CID=$(curl -sS -X POST http://127.0.0.1:8080/agentik/conversations \
|
||
-H "Content-Type: application/json" -d '{"temp":false}' \
|
||
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Привет! 2+2=?"}]'
|
||
sleep 6
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z"
|
||
# → должен быть user + assistant_message с "4"
|
||
|
||
# 3. SSE live
|
||
(curl -sN --max-time 8 "http://127.0.0.1:8080/agentik/conversations/$CID/events?after=1970-01-01T00:00:00Z" \
|
||
> /tmp/sse.out 2>&1) &
|
||
sleep 1
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Скажи ок"}]'
|
||
wait
|
||
cat /tmp/sse.out
|
||
# → start_reasoning, start_response, append_text, end
|
||
|
||
# 4. multi-turn
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"А 3+3?"}]'
|
||
sleep 6
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
|
||
| python3 -c "import sys,json;m=json.load(sys.stdin);print(m[-1])"
|
||
# → assistant_message с "6"
|
||
|
||
# 5. interrupt
|
||
(curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/messages" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"type":"text","body":"Длинная история про драконов, 1000 слов"}]' >/dev/null) &
|
||
sleep 3
|
||
curl -sS -X POST "http://127.0.0.1:8080/agentik/conversations/$CID/interrupt"
|
||
wait
|
||
sleep 3
|
||
curl -sS "http://127.0.0.1:8080/agentik/conversations/$CID/messages?after=1970-01-01T00:00:00Z" \
|
||
| python3 -c "import sys,json;m=json.load(sys.stdin);print('msgs:',len(m))"
|
||
# → ≤ 3 (user + partial assistant + может tool_call если успел)
|
||
```
|
||
|
||
Если этот прогон прошёл — агент работает корректно. Более глубокие
|
||
кейсы — выше по разделам.
|
||
|
||
---
|
||
|
||
## Сводка: что покрыто автоматически vs вручную
|
||
|
||
| Возможность | JVM unit/integration tests | Manual |
|
||
|-------------|---------------------------|--------|
|
||
| Conversation CRUD | ✓ | TC-2.x |
|
||
| send/messages pagination | ✓ | TC-3.x |
|
||
| SSE event format | ✗ | TC-4.x |
|
||
| Memory tools | ✓ (in-memory) | TC-5.x (real backend) |
|
||
| Skills tools | ✓ (in-memory) | TC-6.x (real dir) |
|
||
| SOUL | ✗ | TC-7.x |
|
||
| Interrupt | ✓ (FakeLiteLlm) | TC-8.x (real LLM) |
|
||
| Compaction | ✓ | TC-10.x (real long context) |
|
||
| Reflection | ✓ | TC-11.x |
|
||
| Skill mining | ✓ | TC-12.x |
|
||
| Token accounting | ✓ | TC-13.x |
|
||
| A2A protocol | ✓ (litert tests) | TC-15.x |
|
||
| Error paths | partial | TC-16.x |
|
||
| Persistence/restart | ✗ | TC-9.x |
|
||
|
||
Всё что помечено ✗ — нужно прогонять руками на реальном окружении.
|