Files
obsidian-vault/personal/projects/balda/balda-valera.md
T

11 KiB
Raw Blame History

Балда / Валера — эксплуатация

Расположение

  • 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= не нужен)

Запуск / рестарт

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))
  • 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 возвращает пустой 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/ssedocker-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 — не перечитывает .env
  • up -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 существующих = все проходят
  1. Добавить поле Tools в openAIChatRequest + openAIToolDefinition
  2. В buildChatRequest читать req.Config.Tools → конвертировать в OpenAI tool definitions
  3. В parseChatResponse парсить tool_calls из ответа (ADK сам выполнит MCP)

Важно: upstream norma v0.0.10 (bf2b25d) уже корректно передаёт MCP тулсеты через agentfactory.hostedToolsets(). Проблема только в том, что OpenAIModel — кастомная реализация, не умеющая прокидывать tools. Если бы использовался Gemini/ACP provider — MCP бы работали.