162 lines
8.4 KiB
Markdown
162 lines
8.4 KiB
Markdown
# Балда / Валера — эксплуатация
|
||
|
||
## Расположение
|
||
- Repo: `~/Developer/balda/` (ветка `main`)
|
||
- Code: upstream `normahq/balda` + `feat/zulip-transport` влит, `normahq/norma` v0.0.10
|
||
- **Рабочий compose**: `~/Docker/balda-agent/docker-compose.yaml` → контейнер `balda-agent-valera-1`
|
||
- Config (persistent): `~/Docker/balda-agent/.config/balda/config.yaml`
|
||
- Env: `~/Docker/balda-agent/.env`
|
||
- Owner: `allowed_owners` в config.yaml (статически, `/start owner=` не нужен)
|
||
|
||
## Запуск / рестарт
|
||
|
||
```bash
|
||
cd ~/Docker/balda-agent
|
||
docker-compose restart # без пересборки, только конфиг/env не менялись
|
||
docker-compose up -d # пересоздать контейнер, перечитать .env
|
||
|
||
# Rebuild после изменений в коде:
|
||
cd ~/Docker/claudio-agent && docker-compose build
|
||
docker tag claudio-agent-claudio:latest balda-agent-valera:latest
|
||
cd ~/Docker/claudio-agent && docker-compose up -d
|
||
cd ~/Docker/balda-agent && docker-compose up -d
|
||
```
|
||
|
||
> Валера и Клавдий — **один образ** (`balda-agent-valera`), собирается из одного Dockerfile (`Dockerfile.claudio`). Разница только в конфиге (`config.yaml`) и `.env`, которые монтируются volumes. Сборка идёт из `~/Docker/claudio-agent/` — там есть build секция.
|
||
|
||
## Архитектура получения сообщений
|
||
Валера — **outgoing webhook bot** (bot_type=3). Zulip отправляет webhook только для @mention и DM.
|
||
|
||
- `events_polling.enabled: false` в config.yaml
|
||
- Сообщения приходят только через **outgoing webhook** (webhook_token в .env)
|
||
- После @mention создаётся сессия; последующие сообщения без @mention обрабатываются
|
||
|
||
**После перезапуска**: написать `@Валера <текст>` чтобы создать сессию.
|
||
|
||
## MCP Obsidian — конфиг
|
||
Balda через SSE подключается к obsidian-mcp. **URL обязательно с `/sse`**:
|
||
|
||
```yaml
|
||
runtime:
|
||
mcp_servers:
|
||
obsidian:
|
||
type: sse
|
||
url: http://obsidian-mcp:3101/sse # <-- без /sse → 404 Not Found
|
||
name: obsidian
|
||
```
|
||
|
||
## Контекст (история сообщений)
|
||
**Работает** (на upstream `normahq/balda` с поддержкой hosted LLM agent sessions). ADK inject'ит историю корректно для OpenAI-совместимых провайдеров.
|
||
|
||
Раньше не работало — исправлено в upstream `32e33a8 fix: support hosted MCP toolsets` / `d774c78 fix: use hosted llmagent sessions`.
|
||
|
||
## Проблема: MCP не работают у Валеры (provider: type=openai)
|
||
|
||
### Коренная причина
|
||
hostedagent (`pkg/runtime/hostedagent/`) не поддерживает MCP инструменты.
|
||
`openAIConstructor` в `agentfactory.go` получает `resolvedMCP map[string]agentconfig.MCPServerConfig`,
|
||
но не передаёт их в `hostedagent.Config` — у Config нет поля для MCP.
|
||
`openAIConstructor` вызывает `newHostedAgent(hostedagent.Config{...})` без MCPServers.
|
||
|
||
Фикс: в `hostedagent.Config` добавить `MCPServers map[string]agentconfig.MCPServerConfig`,
|
||
а в `openAIConstructor` передавать `toRuntimeMCPServers(resolvedMCP)`.
|
||
|
||
Важно: hostedagent использует OpenAI-compatible API (не ACP). MCP инструменты нужно
|
||
интегрировать через OpenAI tool_calls — модель шлёт tool_call, код выполняет MCP вызов.
|
||
|
||
## Проблема: MCP не работают у Валеры — попытка фикса
|
||
|
||
### Что было сделано (2026-06-19)
|
||
|
||
#### 1. go.mod
|
||
Добавлен `replace github.com/normahq/norma => ../norma-local` — для локальной разработки.
|
||
|
||
#### 2. hostedagent — добавлена поддержка MCP
|
||
- `pkg/runtime/hostedagent/agent.go`:
|
||
- В Config добавлено `MCPServers map[string]acpagent.MCPServerConfig`
|
||
- Сохранено в `runtimeAgent`
|
||
- В `run()`: после создания модели вызывается `model.SetTools(openAIToolsFromMCPServers(r.MCPServers))`
|
||
|
||
- `pkg/runtime/hostedagent/openai.go`:
|
||
- В `openAIChatRequest` добавлено `Tools []openAIToolDefinition`
|
||
- В `OpenAIModel` добавлены `tools []openAIToolDefinition + `SetTools()`
|
||
- Добавлен `tool_calls` в парсинг ответа (`tool_calls` → content string)
|
||
|
||
#### 3. agentfactory — передача MCP
|
||
- В `openAIConstructor` добавлено `MCPServers: toRuntimeMCPServers(resolvedMCP)` в вызов `newHostedAgent()`
|
||
|
||
#### 4. Dockerfile
|
||
В `Dockerfile.claudio` добавлен `COPY norma-local/ ./norma-local/` — чтобы `replace` работал в контейнере.
|
||
|
||
#### 5. Сборка
|
||
Образ собран и запущен как `balda-agent-valera:latest`.
|
||
|
||
#### 6. Диагностика
|
||
- `mcpServerIDs` приходят корректно: `[balda obsidian fast-rlm]`
|
||
- Добавлен stderr-лог в `agentfactory.go` в `Build()`:
|
||
```go
|
||
fmt.Fprintf(os.Stderr, "BALDA-DEBUG: mcpServerIDs=%v resolvedMCP=%v\n", mcpServerIDs, resolvedMCP)
|
||
```
|
||
- Для просмотра логов нужен `docker logs balda-agent-valera-1 2>&1 | grep BALDA-DEBUG`
|
||
|
||
### Почему не заработало
|
||
Точная причина не установлена — нужно увидеть `resolvedMCP` в логах контейнера. Возможные варианты:
|
||
- `resolveMCPServers` возвращает пустой map
|
||
- `toRuntimeMCPServers` неправильно конвертирует
|
||
- `SetTools` не влияет на уже созданные сообщения в сессии
|
||
|
||
### Что осталось
|
||
1. Прочитать stderr из контейнера: `docker logs balda-agent-valera-1 2>&1 | grep BALDA-DEBUG`
|
||
2. Если `resolvedMCP` пустой — исправлять цепочку resolveMCPServers
|
||
3. Если не пустой — тестировать response с tool_calls в DeepSeek ответе
|
||
|
||
## Диагностика молчания
|
||
|
||
### 1. MCP ошибка: `failed to list MCP tools: failed to connect: Not Found`
|
||
**Причина**: в config.yaml url без `/sse` на конце.
|
||
**Фикс**: `url: http://obsidian-mcp:3101/sse` → `docker-compose restart`.
|
||
|
||
### 2. В логах `command running → command handled` за секунду, без `received provider event`
|
||
**Причина**: стухший embedded NATS. Swarm внутри процесса не доставляет команду до session/task actor'ов.
|
||
**Фикс**: `docker-compose restart` (если не помогло → `up -d`).
|
||
|
||
### 3. Удалён / пустой state.db
|
||
После удаления balda не может зарегистрировать swarm акторы.
|
||
**Фикс**: `rm state.db` и `up -d` (balda создаст заново).
|
||
|
||
### 4. Нет owner
|
||
`handleMessage` выходит при `getOwnerID() == 0`.
|
||
**Фикс**: прописать `allowed_owners` в config.yaml (не нужен `/start owner=`).
|
||
|
||
### 5. stream_only_with_session
|
||
Сообщение в теме без сессии молча дропается.
|
||
**Фикс**: написать `@Валера <текст>` чтобы создать сессию.
|
||
|
||
### 6. Проверить webhook со стороны
|
||
```bash
|
||
curl -X POST http://localhost:8091/zulip/webhook \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"type":"test"}'
|
||
```
|
||
|
||
## Ключевые грабли
|
||
|
||
### OPENAI_BASE_URL — обязательная env var
|
||
Norma не читает `base_url` из config.yaml для `provider: deepseek`. Читает env `OPENAI_BASE_URL`.
|
||
Без неё запросы уходят на `api.openai.com` (401).
|
||
|
||
**В `.env` обязательно:**
|
||
```
|
||
OPENAI_BASE_URL=https://api.deepseek.com/v1
|
||
```
|
||
|
||
### DOCKER_OPTS — пустая строка убивает старт
|
||
Должен быть валидный JSON:
|
||
```
|
||
DOCKER_OPTS={"max_tokens": 2048, "temperature": 0.7}
|
||
```
|
||
|
||
### composerestart vs compose up -d
|
||
- `restart` — не перечитывает `.env`
|
||
- `up -d` — пересоздаёт контейнер с обновлённым `.env`
|