skills: каталог + ленивая загрузка read_skill
Пользовательские инструкции («навыки») живут в указанной папке
(AGENTIK_SKILLS_DIR), рекурсивно читаются при старте и попадают в
системный промпт в сжатом виде: только имя + краткое описание.
Полный текст модель подгружает по требованию, вызывая встроенный
инструмент read_skill(name).
*:skills
- SkillCatalog + SkillPrompt (commonMain): рендер секции системного
промпта; тело навыка в промпт не течёт.
- SkillParser.parseAuto(): теперь читает и opencode-стиль SKILL.md
(YAML frontmatter + markdown тело), и голый *.yaml/*.yml
(поля name, description, опц. body). parseOrThrow для strict-путей.
- SkillParseError.render(): человекочитаемое описание ошибки для
логов и диагностики.
- SkillLoader (jvmMain): рекурсивный обход папки, детерминированный
порядок (по пути), ошибки отдельных файлов не валят загрузку;
дубликаты имён → ошибка, выигрывает первый по пути.
* :standalone
- AgentikConfig.skillsDir + env AGENTIK_SKILLS_DIR.
- ChatAgent: параметр skills (SkillCatalog); системный промпт
автоматически дополняется секцией «## Навыки» и в working memory
сидится вместе с базовым промптом.
- При непустом каталоге в tools автоматически добавляется
SkillReadTool (имя read_skill) — модель может загрузить полный
текст навыка, как обычный LiteTool.
- Main.kt: загружает навыки и шумно логирует ошибки загрузки в stderr.
* docs
- STANDALONE.md: секция «Навыки (skills)», env-переменная в таблице.
- Формат SKILL.md (opencode frontmatter) + голый *.yaml/*.yml.
Тесты: :skills jvmTest 34, :standalone jvmTest 69 (новые — состав
системного промпта, регистрация read_skill, навыки не утекают в
промпт телом).
This commit is contained in:
@@ -16,6 +16,9 @@
|
||||
:server — HTTP+SSE фасад :proto
|
||||
public Route.agentikAgent(agent, path = "/agentik")
|
||||
|
||||
:skills — парсер и каталог навыков (KMP, commonMain + jvmMain-загрузчик)
|
||||
SKILL.md (opencode frontmatter) / *.yaml, renderSystemPromptSection()
|
||||
|
||||
:standalone — JVM-рантайм с реальным LLM-агентом
|
||||
ChatAgent + ChatConversation поверх SQLite и litert-* (openai/google)
|
||||
default 8080:
|
||||
@@ -220,6 +223,7 @@ suspend fun touch(id: String, now: Instant)
|
||||
| `AGENTIK_LLM_BACKEND` | `openai` или `google` | `openai` |
|
||||
| `AGENTIK_SYSTEM_PROMPT` | текст системного промпта | «Ты полезный ассистент. Отвечай кратко и по делу.» |
|
||||
| `AGENTIK_MCP_CONFIG` | путь к `mcp.json` в формате Claude Desktop (`{"mcpServers":{"name":{"command":"...","args":[...]}` или `"url":"..."}`) | не задан (MCP выключен) |
|
||||
| `AGENTIK_SKILLS_DIR` | папка с навыками (рекурсивно; `SKILL.md` или `*.yaml`/`*.yml`) | не задано (навыков нет) |
|
||||
|
||||
### Backend `openai`
|
||||
|
||||
@@ -281,6 +285,43 @@ val agent = ChatAgent(id, stores, llm, llmConfig, tools = tools)
|
||||
|
||||
ID у `ToolCall` и `ToolResult` разные (`tc-…` / `tr-…`), но `MessageRecord.ToolResult.toolCallId` указывает на `MessageRecord.ToolCall.id` той же логической пары. Этим достигается уникальность PK в таблице `message`.
|
||||
|
||||
### Навыки (skills)
|
||||
|
||||
Навыки — это «лениво загружаемые» инструкции: в системный промпт попадают только **имя + краткое описание**, а полный текст модель достаёт сама, вызывая встроенный инструмент `read_skill`.
|
||||
|
||||
**Формат файла** (два варианта, оба читаются):
|
||||
|
||||
1. opencode-style `SKILL.md` — YAML-frontmatter + markdown-тело:
|
||||
```markdown
|
||||
---
|
||||
name: backend:spring:db-base
|
||||
description: MUST load before any database work.
|
||||
---
|
||||
|
||||
# ... полный текст навыка ...
|
||||
```
|
||||
2. Голый YAML `*.yaml` / `*.yml` — поля `name`, `description`, опционально `body`:
|
||||
```yaml
|
||||
name: lint
|
||||
description: Run the linter before committing.
|
||||
body: |
|
||||
# Lint
|
||||
Run `./gradlew detekt`.
|
||||
```
|
||||
|
||||
Загрузка: `SkillLoader.loadDirectory(dir)` рекурсивно обходит `AGENTIK_SKILLS_DIR`, парсит файлы и возвращает `SkillCatalog` + список ошибок (битый файл не валит загрузку, дубликат имени — ошибка, выигрывает первый по пути).
|
||||
|
||||
**Что попадает в системный промпт** (`SkillCatalog.renderSystemPromptSection()`): блок `## Навыки` со списком `- **name**: description`. Тело навыка в промпт НЕ попадает.
|
||||
|
||||
**Инструмент `read_skill`** (`SkillReadTool`) регистрируется в `ChatAgent` автоматически, если каталог непустой; для модели он выглядит как обычная функция с аргументом `{"name": "<skill>"}`. Модель вызывает его по необходимости, результат возвращается как обычный tool-result (см. tool-loop ниже).
|
||||
|
||||
```kotlin
|
||||
val skills = SkillLoader.loadDirectory(File(System.getenv("AGENTIK_SKILLS_DIR"))).catalog
|
||||
val agent = ChatAgent(id, stores, llm, llmConfig, tools = mcpRegistry.namedTools, skills = skills)
|
||||
```
|
||||
|
||||
Навык можно передать и напрямую «в тулзах» — `SkillReadTool` достаточно обернуть в `NamedTool(SkillReadTool.NAME, SkillReadTool(catalog))`, но при непустом `skills`-параметре это делается за вас.
|
||||
|
||||
### Добавить ещё один транспорт
|
||||
|
||||
Каждый транспорт — отдельный модуль, который получает `Agent` и сериализует его под свой протокол:
|
||||
|
||||
Reference in New Issue
Block a user