Практическое руководство

Запускаем агентную систему на Go с Seshat

Уровень: продвинутый Чтение: до 12 минут Результат: минимальный агент, модель, инструмент и обработка ошибок

Первый прототип часто застревает не на модели, а на обвязке: отдельно создаются цикл сообщений, реестр инструментов, разрешения, сессии, MCP-клиент и RAG. Seshat объединяет эти части в один агентный рантайм. Ниже мы встроим его в программу на Go, дадим агенту прочитать локальный файл и увидим события выполнения.

Что мы собираем

запрос пользователя
        │
        ▼
   sdk.Session
        │
        ├── модель
        ├── история сессии
        ├── политика разрешений
        └── встроенный read_file
                    │
                    ▼
              локальный файл

Seshat — открытый рантайм на Go, а не отдельная модель. Он связывает провайдера LLM, цикл вызова инструментов, сессии, разрешения и события выполнения. В том же ядре предусмотрены MCP, RAG, память и другие инструменты, но для первого запуска они не нужны.

Мы намеренно используем встроенный инструмент чтения файла. Он дает наблюдаемый вызов без shell-команд, сетевых запросов и внешних MCP-серверов. Код ниже является минимальным учебным примером, а не готовой производственной конфигурацией.

1. Проверьте окружение

Понадобятся Go, Git и ключ поддерживаемого провайдера. Вместо предполагаемой версии Go сначала проверьте локальную установку и требования текущего модуля Seshat:

go version
git --version
go env GOPATH GOPROXY

Если Go отсутствует или зависимость сообщает о несовместимой версии, установите поддерживаемую версию по официальной документации Go, затем повторите команды. Не отключайте проверку модулей и не используйте неизвестный прокси только ради успешной сборки.

2. Создайте изолированный проект

Работайте в новом каталоге: так агент не получит случайный доступ к домашней папке или рабочему монорепозиторию.

mkdir seshat-go-lab
cd seshat-go-lab
go mod init example.com/seshat-go-lab
go get github.com/KPO-Tech/seshat@latest

Команда с @latest выбирает актуальную опубликованную версию на момент запуска. Для воспроизводимой командной сборки после проверки сохраните получившиеся go.mod и go.sum в системе контроля версий: они зафиксируют реально разрешенную версию зависимости.

Создайте файл, содержание которого нельзя угадать из запроса:

printf '%s\n' 'LAB_CODE=cedar-417' > fixture.txt

Здесь cedar-417 — публичное тестовое значение, а не пароль или секрет. Не помещайте настоящие ключи в файл, который агенту разрешено читать.

3. Передайте ключ модели безопасно

Пример использует Anthropic, потому что такой вариант показан в публичной документации Go SDK Seshat. Значение ключа в статье намеренно отсутствует:

read -s ANTHROPIC_API_KEY
export ANTHROPIC_API_KEY
printf '\nКлюч загружен в окружение текущей оболочки\n'

Вставьте ключ после первой команды и нажмите Enter. read -s не печатает ввод на экран. Не добавляйте ключ в исходный код, историю shell, go.mod или Git.

Для другого провайдера замените идентификатор, модель и переменную окружения по актуальному разделу Providers & Auth в документации Seshat. Имена моделей меняются; сверяйте их с провайдером перед запуском.

4. Соберите минимального агента

Сохраните следующий пример как main.go. Он проверяет конфигурацию до создания клиента, ограничивает продолжительность запроса, печатает runtime-события и различает ошибки инициализации, сессии и выполнения.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/KPO-Tech/seshat/pkg/sdk"
)

func main() {
	if err := run(); err != nil {
		log.Printf("agent failed: %v", err)
		os.Exit(1)
	}
}

func run() error {
	apiKey := os.Getenv("ANTHROPIC_API_KEY")
	if apiKey == "" {
		return errors.New("ANTHROPIC_API_KEY is empty")
	}

	workingDir, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("resolve working directory: %w", err)
	}

	client, err := sdk.NewClient(&sdk.ClientConfig{
		Model: sdk.ModelIdentifier{
			Provider: sdk.APIProviderAnthropic,
			Model:    "claude-sonnet-4-20250514",
		},
		APIKey:         apiKey,
		WorkingDir:     workingDir,
		PermissionMode: sdk.PermissionModeOnRequest,
		RuntimeEventFn: func(event sdk.RuntimeEvent) {
			log.Printf("runtime event: %+v", event)
		},
	})
	if err != nil {
		return fmt.Errorf("create Seshat client: %w", err)
	}
	defer client.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
	defer cancel()

	session, err := client.CreateSession(ctx)
	if err != nil {
		return fmt.Errorf("create session: %w", err)
	}
	defer session.Close()

	prompt := `Прочитай файл fixture.txt встроенным инструментом.
Верни только значение LAB_CODE. Не угадывай и не запускай shell-команды.`

	response, err := session.SubmitMessage(ctx, prompt)
	if err != nil {
		if errors.Is(err, context.DeadlineExceeded) {
			return errors.New("agent turn exceeded the 90-second deadline")
		}
		return fmt.Errorf("submit message: %w", err)
	}

	fmt.Printf("answer: %s\n", response.Content)
	return nil
}

WorkingDir задает рабочую область, но сам по себе не является полноценной песочницей. PermissionModeOnRequest сохраняет контроль над чувствительными действиями. Для серверного приложения потребуется собственная политика подтверждений: фоновый worker не должен бесконечно ждать интерактивного ответа.

Обработчик RuntimeEventFn печатает структуру события целиком. Это диагностический прием: он не зависит от того, какие поля вы захотите вынести в метрики позднее. В рабочей системе фильтруйте события и не записывайте в журнал содержимое секретных файлов или аргументы с токенами.

5. Сначала проверьте сборку

gofmt -w main.go
go mod tidy
go build -o seshat-lab .
go vet ./...

Эти команды только форматируют и анализируют локальный учебный модуль. go build проверяет соответствие примера установленной версии API. Если публичный API Seshat изменился, компилятор покажет конкретный символ, который нужно сверить с документацией версии из go.mod.

Посмотрите, какая версия действительно выбрана:

go list -m github.com/KPO-Tech/seshat
go mod verify

6. Запустите агента и проверьте вызов

./seshat-lab

Успешная проверка состоит из двух независимых признаков:

  1. В stderr появились runtime-события, относящиеся к вызову встроенного файлового инструмента.
  2. Финальная строка содержит answer: cedar-417.

Одного правильного ответа недостаточно: модель теоретически могла воспроизвести значение из прежнего контекста. Поэтому повторите опыт с новым значением:

printf '%s\n' 'LAB_CODE=maple-982' > fixture.txt
./seshat-lab

Теперь ожидается answer: maple-982 и новый инструментальный вызов. Если ответ остался прежним, проверьте путь, сессию и кэширование, а не исправляйте промпт вслепую.

7. Проверьте контролируемые ошибки

Нет ключа

env -u ANTHROPIC_API_KEY ./seshat-lab
printf 'exit code: %s\n' "$?"

Программа должна завершиться с ненулевым кодом и сообщением ANTHROPIC_API_KEY is empty. Запрос к провайдеру выполняться не должен.

Нет файла

mv fixture.txt fixture.txt.off
./seshat-lab
mv fixture.txt.off fixture.txt

Корректное поведение — явное сообщение о невозможности прочитать файл или ошибка хода. Агент не должен придумывать LAB_CODE. Команда восстановления вынесена отдельно, чтобы файл можно было вернуть даже после неуспешного запуска.

Недоступен провайдер

Ошибка авторизации, лимит запросов, сетевой сбой или неизвестная модель должны попасть в ветку submit message либо create Seshat client и завершить процесс с кодом 1. Не печатайте ключ при диагностике и не повторяйте запрос бесконечно.

Истек таймаут

По истечении 90 секунд контекст отменяется, а программа возвращает отдельную ошибку. Для production-сервиса добавьте ограниченное число повторов только для временных сбоев, экспоненциальную задержку и общий дедлайн задачи. Ошибки конфигурации и авторизации повторять автоматически бессмысленно.

Что именно взял на себя Seshat

В ручной реализации пришлось бы отдельно написать цикл «сообщение — ответ модели — tool call — результат инструмента — продолжение ответа», привести схемы инструментов к формату провайдера и решить, когда цикл должен остановиться. В примере эти обязанности находятся за интерфейсом sdk.Client и сессии.

Это не устраняет архитектурные решения. Приложение по-прежнему отвечает за секреты, границы рабочей директории, пользовательскую авторизацию, допустимые инструменты, лимиты, хранение журналов и реакцию на сбои.

Как расширять прототип

MCP

MCP нужен, когда инструмент живет вне процесса: например, отдельный сервер публикует операции над внутренней системой. Seshat умеет подключать MCP-серверы, выполнять handshake, обнаруживать операции и добавлять их в общий реестр инструментов.

Не подключайте произвольный MCP-сервер сразу с правами записи. Сначала зафиксируйте его команду запуска или URL, версию, список ожидаемых инструментов, таймаут и правила разрешений. После подключения сравните обнаруженный список с allowlist.

RAG

RAG полезен для поиска релевантных фрагментов в корпусе документов. Он не заменяет инструмент: поиск предоставляет контекст, а инструмент выполняет действие. Начинайте с отдельного тестового корпуса без персональных данных и проверяйте не только ответ, но и выбранные фрагменты.

Собственный инструмент

Публичный SDK предоставляет регистрацию инструментов на уровне клиента и сессии. При добавлении собственного обработчика задайте узкую JSON-схему, валидируйте аргументы повторно внутри Go-кода и возвращайте структурированную ошибку. Описание инструмента для модели не является механизмом безопасности.

Актуальные сигнатуры регистрации и управление поверхностью инструментов сверяйте с разделом Tools & Surface. Не копируйте сигнатуру из статьи для другой версии модуля без проверки go.mod.

Типовые ошибки

Модель отвечает без вызова инструмента
Убедитесь, что значение отсутствует в запросе, инструмент доступен сессии, а выбранная модель поддерживает tool calling. Смотрите runtime-события: красивый ответ не доказывает выполнение.
Агент видит слишком много файлов
Не запускайте пример из домашнего каталога или корня репозитория. Создавайте отдельную рабочую директорию и формируйте явный список разрешенных инструментов для каждой роли.
Процесс ожидает подтверждение без интерфейса
Режим OnRequest требует продуманного канала одобрения чувствительных действий. В headless-сервисе возвращайте задачу в состояние ожидания или заранее запрещайте такие инструменты.
Код из документации не собирается
Сравните версию из go list -m с документацией и исходным кодом этой версии. Seshat развивается, поэтому имена моделей и части SDK могут изменяться.
В лог попали аргументы инструмента
Диагностическая печать %+v подходит только для стенда без секретов. В production сохраняйте тип, длительность, статус и идентификатор вызова, а чувствительные поля редактируйте или исключайте.
Каждая ошибка автоматически повторяется
Разделяйте временные сетевые сбои, rate limit, неверный ключ, запрещенный инструмент и ошибочные аргументы. У каждого класса должны быть собственная политика повторов и предел попыток.

Ограничения минимальной сборки

  • Пример запускает один процесс и одну сессию; он не решает многопользовательскую изоляцию.
  • Рабочая директория ограничивает контекст, но не заменяет контейнер, системную песочницу и политику сетевого доступа.
  • Вывод runtime-событий является диагностикой, а не полноценной трассировкой или аудитом.
  • В примере нет бюджета токенов, очереди задач, повторов, circuit breaker и долговременного хранилища результатов.
  • Качество выбора инструментов зависит от модели, формулировки задачи и схемы инструмента.
  • MCP и RAG поддерживаются рантаймом, но намеренно не включены в минимальный запуск.
  • Перед production-внедрением нужно закрепить версию зависимости и провести собственные интеграционные и негативные проверки.

Критерий готовности прототипа

Минимальный стенд можно считать рабочим, если он собирается из чистого каталога, завершает запрос в пределах дедлайна, показывает фактический вызов read_file, возвращает текущее значение из fixture.txt и предсказуемо падает при отсутствии ключа или файла.

Следующий разумный шаг — не добавлять сразу десятки инструментов, а зарегистрировать одну прикладную read-only операцию, оставить runtime-события включенными и проверить разрешенный вызов, запрещенный вызов, неправильные аргументы, таймаут и недоступность провайдера.

Официальные материалы