11 KiB
Балда / Валера — эксплуатация
Расположение
- Repo:
~/Developer/balda/(веткаmain) - Code: upstream
normahq/balda+feat/zulip-transportвлит,normahq/normav0.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=не нужен)
Запуск / рестарт
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:
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))
- В Config добавлено
-
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():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возвращает пустой maptoRuntimeMCPServersнеправильно конвертируетSetToolsне влияет на уже созданные сообщения в сессии
Что осталось
- Прочитать stderr из контейнера:
docker logs balda-agent-valera-1 2>&1 | grep BALDA-DEBUG - Если
resolvedMCPпустой — исправлять цепочку resolveMCPServers - Если не пустой — тестировать 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 со стороны
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— не перечитывает.envup -d— пересоздаёт контейнер с обновлённым.env
Диагноз: MCP не работают — инструменты не передаются модели
Статус (2026-06-21): MCP резолвятся, но не вызываются.
Установлено через BALDA-DEBUG:
BALDA-DEBUG: mcpServerIDs=[balda obsidian fast-rlm]
resolvedMCP=map[balda:{http http://127.0.0.1:33545/mcp}
fast-rlm:{http http://fast-rlm-mcp:3333/mcp}
obsidian:{sse http://obsidian-mcp:3101/sse}]
MCP серверы зарезолвлены, тулсеты переданы в hostedagent.Config.
Почему не работают:
OpenAIModel.generate() → buildChatRequest() не читает req.Config.Tools (поле genai.GenerateContentConfig). ADK кладёт инструменты туда, но buildChatRequest сериализует только model, messages, temperature, top_p, max_tokens, stop. DeepSeek получает запрос без tools → отвечает текстом.
Логи подтверждают: function_call_part_count=0 в ответе DeepSeek.
Фикс: добавить передачу tools в OpenAI запрос и парсинг tool_calls из ответа.
Сделано (2026-06-21): в openai.go добавлены:
openAIToolDefinition,openAIFunction,openAIToolCall— структуры для API- Поле
ToolsвopenAIChatRequest - Функция
openAIToolsFromConfig()— конвертируетgenai.Tool[]→ OpenAI tool definitions - Функция
contentToOpenAI()— обрабатывает FunctionCall/FunctionResponse из ADK - Парсинг
tool_callsвparseChatResponse() - Тесты: 17 тестов на new функциональность + 3 существующих = все проходят
- Добавить поле
ToolsвopenAIChatRequest+openAIToolDefinition - В
buildChatRequestчитатьreq.Config.Tools→ конвертировать в OpenAI tool definitions - В
parseChatResponseпарситьtool_callsиз ответа (ADK сам выполнит MCP)
Важно: upstream norma v0.0.10 (bf2b25d) уже корректно передаёт MCP тулсеты через agentfactory.hostedToolsets(). Проблема только в том, что OpenAIModel — кастомная реализация, не умеющая прокидывать tools. Если бы использовался Gemini/ACP provider — MCP бы работали.