standalone: add MCP client + tool-call loop
- Add mcp/McpConfig + McpRegistry wrapping io.modelcontextprotocol:kotlin-sdk-client 0.15.0
- Supports stdio (uvx/npx/python) and streamable HTTP transports
- Claude Desktop-compatible JSON config (AGENTIK_MCP_CONFIG)
- server__tool name prefix to avoid collisions between servers
- Add agent/NamedTool (name + LiteTool pair) and tools: List<NamedTool> on ChatAgent/ChatConversation
- Implement tool-call loop in ChatConversation.runTurn:
- delta.toolCalls -> emit ToolCall event -> persist audit -> execute tool
-> emit ToolResult -> persist -> liteConv.addToolResult(callId, name, result)
- separate tc-/tr- prefixes keep SQL PRIMARY KEY unique while toolCallId FK is preserved
- 8 McpConfig + 4 McpRegistry unit tests; +1 ChatAgentTest tool-loop test (44/44 total)
- e2e verified: real MCP fetch server (mcp-server-fetch) + litellm local/codding
-> LLM calls fetch__fetch, MCP exec, result fed back, conversation continues
docs/STANDALONE.md: drop 'no tools / no MCP' from §8; replace 'Подключить тул (v2)' stub
with full in-agent + MCP recipe and tool-loop algorithm in §7
This commit is contained in:
+28
-6
@@ -219,6 +219,7 @@ suspend fun touch(id: String, now: Instant)
|
||||
| `AGENTIK_DB_PATH` | путь к SQLite-файлу | `./agentik.db` |
|
||||
| `AGENTIK_LLM_BACKEND` | `openai` или `google` | `openai` |
|
||||
| `AGENTIK_SYSTEM_PROMPT` | текст системного промпта | «Ты полезный ассистент. Отвечай кратко и по делу.» |
|
||||
| `AGENTIK_MCP_CONFIG` | путь к `mcp.json` в формате Claude Desktop (`{"mcpServers":{"name":{"command":"...","args":[...]}` или `"url":"..."}`) | не задан (MCP выключен) |
|
||||
|
||||
### Backend `openai`
|
||||
|
||||
@@ -248,14 +249,37 @@ suspend fun touch(id: String, now: Instant)
|
||||
|
||||
## 7. Расширение
|
||||
|
||||
### Подключить тул (v2)
|
||||
### Подключить тулы (MCP / in-agent)
|
||||
|
||||
```kotlin
|
||||
val tools: List<LiteTool> = listOf(ReadFileTool(Path.of("/work")))
|
||||
val cfg = LiteConversationConfig(systemInstruction = "…", tools = tools)
|
||||
// 1) In-agent tool (нативный LiteTool):
|
||||
val echoTool = object : LiteTool {
|
||||
override fun describe(): String =
|
||||
"""{"type":"object","properties":{"x":{"type":"string"}},"required":["x"]}"""
|
||||
override fun invoke(args: String): String = "echoed: $args"
|
||||
}
|
||||
val tools = listOf(NamedTool("echo", echoTool))
|
||||
|
||||
// 2) MCP (stdio / streamable HTTP) — конфиг в Claude Desktop-формате:
|
||||
val mcp = McpConfig.fromEnv() // читает AGENTIK_MCP_CONFIG=path/to/mcp.json
|
||||
val registry = McpRegistry.fromConfig(mcp) // стартует все серверы, лист LiteTool'ов
|
||||
val tools = registry.namedTools // server__tool префикс автоматически
|
||||
|
||||
// 3) В обоих случаях:
|
||||
val agent = ChatAgent(id, stores, llm, llmConfig, tools = tools)
|
||||
```
|
||||
|
||||
LiteConversation сообщит модели о доступных тулах. После появления `LiteContentPart.ToolResult` (v2) появится и `ChatConversation.addToolResult(...)` — ручной tool-loop на on-device движке без пересоздания беседы.
|
||||
`LiteConversation` принимает `tools = ...` в `LiteConversationConfig`. На каждый `delta.toolCalls` из модели `ChatConversation.runTurn`:
|
||||
|
||||
1. Эмитит `Event.ToolCall(callId, toolName, argsJson)` клиенту (по SSE)
|
||||
2. Записывает `MessageRecord.ToolCall` в audit + working memory (если не temp)
|
||||
3. Вызывает `tool.invoke(argsJson)`
|
||||
4. Эмитит `Event.ToolResult(resultId, resultText)`
|
||||
5. Записывает `MessageRecord.ToolResult` (с `toolCallId = callId`)
|
||||
6. Кормит `liteConv.addToolResult(callId, name, result)` в LiteConversation (KV-cache выживает между итерациями)
|
||||
7. Цикл повторяется до `delta.toolCalls.isEmpty()`
|
||||
|
||||
ID у `ToolCall` и `ToolResult` разные (`tc-…` / `tr-…`), но `MessageRecord.ToolResult.toolCallId` указывает на `MessageRecord.ToolCall.id` той же логической пары. Этим достигается уникальность PK в таблице `message`.
|
||||
|
||||
### Добавить ещё один транспорт
|
||||
|
||||
@@ -283,9 +307,7 @@ LiteConversation сообщит модели о доступных тулах.
|
||||
|
||||
## 8. Что НЕ делает `standalone` сегодня
|
||||
|
||||
* **Нет инструментов (тулов).** Поддержка ждёт `LiteContentPart.ToolResult` в litert-api (v2). До тех пор агент — text-only чат.
|
||||
* **Нет суммаризации.** `WorkingMemoryStore.compact` уже есть, но без LLM-вызова для генерации текста суммаризации.
|
||||
* **Нет MCP-клиента.**
|
||||
* **Нет авторизации.** Все эндпоинты открыты.
|
||||
* **Нет инкрементальной догрузки старых сообщений.** `getMessages(after)` работает с offset/limit, но без «схлопывания» (compaction в визуальной истории — задача клиента).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user