Разработка ИИ-агентов · Модуль 1 · Урок 1.2
Вызов LLM API напрямую на Go: messages, роли, параметры
Модель общается списком сообщений
Современные чат-модели принимают на вход не одну строку, а список сообщений. Каждое сообщение имеет роль (role) и содержимое (content). Базовые роли: system — инструкции и рамки поведения (задаются один раз в начале), user — реплики пользователя, assistant — предыдущие ответы самой модели. Чуть позже добавится роль tool — для результатов инструментов.
Важно понять: API без состояния (stateless). Модель не «помнит» прошлый разговор сама — всю историю вы передаёте заново при каждом запросе. Поэтому «память» агента — это просто растущий слайс сообщений, который вы аккуратно ведёте на стороне Go. Это и хорошо (полный контроль), и накладно (за каждый токен истории вы платите снова).
OpenAI-совместимый запрос на Go
Мы будем ходить в модель через OpenRouter — это шлюз с OpenAI-совместимым Chat Completions API. Запрос — это обычный HTTP POST с JSON-телом: поле model (слаг модели), messages (список) и параметры генерации. Ответ содержит choices, внутри — message с ответом и finish_reason (почему модель остановилась).
Слаги моделей и точные имена параметров версионно-зависимы — сверяйтесь с актуальными доками провайдера. На момент написания у Anthropic это семейства claude-sonnet-4.x / claude-opus-4.x / claude-haiku-4.x; в OpenRouter они адресуются как anthropic/claude-.... Не хардкодьте слаг «навечно» — выносите в конфигурацию.
Параметры генерации
Несколько параметров, которые стоит знать сразу:
- temperature — «креативность». Около 0 ответы детерминированнее и стабильнее (это нужно агентам, где важна предсказуемость), выше — разнообразнее, но менее надёжно.
- max_tokens — потолок длины ответа. Защищает от слишком длинной (и дорогой) генерации.
- system-сообщение — самый сильный рычаг поведения: роль агента, что можно и нельзя, формат ответа.
Для агентов почти всегда берут низкую температуру: цикл и так добавляет неопределённости, и лишний разброс на каждом шаге вредит.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := map[string]any{
"model": "anthropic/claude-sonnet-4.6", // сверьтесь с актуальным слагом
"messages": []map[string]string{
{"role": "system", "content": "Ты лаконичный помощник. Отвечай по делу."},
{"role": "user", "content": "Объясни в двух предложениях, что такое агентный цикл."},
},
"temperature": 0,
"max_tokens": 300,
}
raw, _ := json.Marshal(body)
req, _ := http.NewRequest("POST",
"https://openrouter.ai/api/v1/chat/completions", bytes.NewReader(raw))
req.Header.Set("Authorization", "Bearer "+os.Getenv("OPENROUTER_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
var out struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
FinishReason string `json:"finish_reason"`
} `json:"choices"`
}
if err := json.Unmarshal(data, &out); err != nil || len(out.Choices) == 0 {
fmt.Println("ответ не разобран:", string(data))
return
}
fmt.Println("Ответ:", out.Choices[0].Message.Content)
fmt.Println("finish_reason:", out.Choices[0].FinishReason)
}Anti-patterns
| Анти-паттерн | Почему плохо | Как правильно |
|---|---|---|
| Хранить ключ API прямо в коде | Утечка секрета в репозиторий | Читать из окружения (os.Getenv), .env — в .gitignore |
| Считать API stateful | Модель «забывает» контекст — ответы рассыпаются | Передавать всю нужную историю сообщений в каждом запросе |
| Хардкодить слаг модели по всему коду | Слаги меняются; правок много, легко забыть | Один источник — конфиг/env; в примерах помечать «сверьтесь с доками» |
| Высокая температура у агента | Нестабильные шаги, плавающее поведение | Низкая температура (≈0) для предсказуемости цикла |
Практическое задание
- Заведите
OPENROUTER_API_KEYв окружении и сделайте первый успешный вызов модели из Go. - Добавьте
system-сообщение и убедитесь, что оно меняет поведение (тон, формат, ограничения). - Проведите мини-диалог из 2 ходов, вручную дозаписывая ответ модели как сообщение с ролью
assistantв историю. - Поэкспериментируйте с
temperature0 и 1 на одном и том же запросе — сравните стабильность ответов. - Выведите
finish_reasonи проверьте, что при обычном ответе он равенstop.
Проверка знаний
Почему историю диалога приходится передавать модели в каждом запросе?
Верный ответ: B
B верно. API не хранит состояние между вызовами, поэтому всю необходимую историю вы передаёте заново. Память агента — это слайс сообщений на вашей стороне. C прямо неверно; A и D к делу не относятся.
Какая роль задаёт модели рамки поведения и обычно ставится в начале списка сообщений?
Верный ответ: D
D верно. Роль system задаёт инструкции и ограничения. user — реплики пользователя, assistant — ответы модели, tool — результаты инструментов (о ней дальше).
Какое значение temperature обычно предпочтительно для агента и почему?
Верный ответ: B
B верно. Агентный цикл и так добавляет неопределённости; низкая температура делает каждый шаг стабильнее. Высокая температура не повышает «интеллект», а увеличивает разброс. C неверно — влияет.