Подключение API Gemini: Пусть Google SDK напрямую подключается к локальному шлюзу
Чему вы научитесь
- Использовать нативные конечные точки Gemini, предоставляемые Antigravity Tools (
/v1beta/models/*) для подключения вашего клиента - Использовать пути в стиле Google
:generateContent/:streamGenerateContentдля вызова локального шлюза - При включении аутентификации Proxy понимать, почему
x-goog-api-keyможно напрямую использовать
Текущие проблемы
Вы могли уже запустить локальный прокси, но как дошли до Gemini, начали застревать:
- Google SDK по умолчанию вызывает
generativelanguage.googleapis.com, как изменить на вашhttp://127.0.0.1:<port>? - Путь Gemini содержит двоеточие (
models/<model>:generateContent), многие клиенты сразу склеивают и получают 404 - Вы включили аутентификацию прокси, но Google-клиент не отправляет
x-api-key, в итоге всегда 401
Когда использовать этот метод
- Вы хотите использовать «нативный протокол Gemini», а не слой совместимости OpenAI/Anthropic
- У вас уже есть клиент в стиле Google/сторонний Gemini, вы хотите с минимальной стоимостью мигрировать на локальный шлюз
🎒 Подготовка
Предварительные условия
- Вы уже добавили хотя бы 1 аккаунт в App (иначе бэкенд не может получить токен доступа вышестоящего уровня)
- Вы уже запустили службу локального прокси и знаете порт прослушивания (по умолчанию будет использовать
8045)
Основная идея
Antigravity Tools экспонирует нативные пути Gemini на локальном сервере Axum:
- Список:
GET /v1beta/models - Вызов:
POST /v1beta/models/<model>:generateContent - Поток:
POST /v1beta/models/<model>:streamGenerateContent
Бэкенд обернёт ваш нативный запрос тела Gemini структурой v1internal (вставит project, requestId, requestType и т.д.), а затем пересылает на конечную точку вышестоящего уровня v1internal (и приносит токен доступа аккаунта). (Исходный код: src-tauri/src/proxy/mappers/gemini/wrapper.rs, src-tauri/src/proxy/upstream/client.rs)
Почему в примерах учебника base URL рекомендует 127.0.0.1?
В примерах быстрой интеграции App жёстко рекомендуется использовать 127.0.0.1, причина — «избежать проблем с задержкой разрешения IPv6 в некоторых средах». (Исходный код: src/pages/ApiProxy.tsx)
Пошаговое руководство
Шаг 1: Подтвердите, что шлюз онлайн (/healthz)
Зачем Сначала подтвердите, что сервис онлайн, затем устраняйте проблемы с протоколом/аутентификацией, это сэкономит много времени.
curl -s "http://127.0.0.1:8045/healthz"Invoke-RestMethod "http://127.0.0.1:8045/healthz"Что вы должны увидеть: Возвращается JSON, содержащий {"status":"ok"} (Исходный код: src-tauri/src/proxy/server.rs).
Шаг 2: Перечислите модели Gemini (/v1beta/models)
Зачем Вам нужно сначала подтвердить «какой идентификатор модели экспонируется», последующие <model> ориентируются на это.
curl -s "http://127.0.0.1:8045/v1beta/models" | headЧто вы должны увидеть: В ответе есть массив models, name каждого элемента похож на models/<id> (Исходный код: src-tauri/src/proxy/handlers/gemini.rs).
Важно
Какой поле использовать для идентификатора модели?
- ✅ Используйте поле
displayName(например,gemini-2.0-flash) - ✅ Или уберите префикс
models/из поляname - ❌ Не копируйте напрямую полное значение поля
name(это приведёт к ошибке пути)
Если вы скопируете поле name (например, models/gemini-2.0-flash) как идентификатор модели, путь запроса станет /v1beta/models/models/gemini-2.0-flash:generateContent, это неправильно. (Исходный код: src-tauri/src/proxy/common/model_mapping.rs)
Важно
Текущий /v1beta/models — это возврат, «который маскирует локальный динамический список моделей как список Gemini», а не реальная загрузка с вышестоящего уровня. (Исходный код: src-tauri/src/proxy/handlers/gemini.rs)
Шаг 3: Вызов generateContent (путь с двоеточием)
Зачем Ключ нативного REST API Gemini — это «action с двоеточием» типа :generateContent. Бэкенд проанализирует model:method в том же маршруте. (Исходный код: src-tauri/src/proxy/handlers/gemini.rs)
curl -s \
-H "Content-Type: application/json" \
-X POST "http://127.0.0.1:8045/v1beta/models/<modelId>:generateContent" \
-d '{
"contents": [
{"role": "user", "parts": [{"text": "Hello"}]}
]
}'Что вы должны увидеть: В JSON ответа есть candidates (или снаружи есть response.candidates, прокси распакует).
Шаг 4: Вызов streamGenerateContent (SSE)
Зачем Поток более стабилен для «длинного вывода/большой модели»; прокси пересылает вышестоящий SSE вашему клиенту и устанавливает Content-Type: text/event-stream. (Исходный код: src-tauri/src/proxy/handlers/gemini.rs)
curl -N \
-H "Content-Type: application/json" \
-X POST "http://127.0.0.1:8045/v1beta/models/<modelId>:streamGenerateContent" \
-d '{
"contents": [
{"role": "user", "parts": [{"text": "Tell me a short story"}]}
]
}'Что вы должны увидеть: Терминал постоянно выводит строки SSE в форме data: {...}, в нормальных условиях в конце появится data: [DONE] (указывает на конец потока).
Внимание
data: [DONE] — это стандартный знак окончания SSE, но не обязательно появится:
- Если вышестоящий уровень нормально заканчивается и отправляет
[DONE], прокси пересылает его - Если вышестоящий уровень аномально разрывается, тайм-аут или отправляет другие сигналы окончания, прокси не будет компенсировать
[DONE]
Код клиента должен обрабатывать по стандарту SSE: при встрече data: [DONE] или разрыве соединения считать поток оконченным. (Исходный код: src-tauri/src/proxy/handlers/gemini.rs)
Шаг 5: Прямое подключение Google Python SDK к локальному шлюзу
Зачем Это путь примера «быстрая интеграция», предоставленный в UI проекта: используйте пакет Google Generative AI Python, чтобы указать api_endpoint на ваш адрес локального прокси. (Исходный код: src/pages/ApiProxy.tsx)
#Нужно установить: pip install google-generativeai
import google.generativeai as genai
genai.configure(
api_key="YOUR_PROXY_API_KEY",
transport='rest',
client_options={'api_endpoint': 'http://127.0.0.1:8045'}
)
model = genai.GenerativeModel('<modelId>')
response = model.generate_content("Hello")
print(response.text)Что вы должны увидеть: Программа выводит фрагмент ответа модели.
Контрольные точки ✅
/healthzможет вернуть{"status":"ok"}/v1beta/modelsможет перечислить модели (минимум 1):generateContentможет вернутьcandidates:streamGenerateContentвозвращаетContent-Type: text/event-streamи может постоянно выдавать поток
Частые ошибки
- 401 всегда не проходит: Если вы включили аутентификацию, но
proxy.api_keyпуст, бэкенд напрямую отклонит запрос. (Исходный код:src-tauri/src/proxy/middleware/auth.rs) - Какой key нести в заголовке: Прокси одновременно распознаёт
Authorization,x-api-key,x-goog-api-key. Поэтому «клиент в стиле Google только отправляетx-goog-api-key» тоже пройдёт. (Исходный код:src-tauri/src/proxy/middleware/auth.rs) - countTokens всегда 0: Текущий
POST /v1beta/models/<model>/countTokensвозвращает фиксированный{"totalTokens":0}, это реализация-заглушка. (Исходный код:src-tauri/src/proxy/handlers/gemini.rs)
Итог урока
- Вы подключаетесь к
/v1beta/models/*, а не/v1/* - Ключевая запись пути —
models/<modelId>:generateContent/:streamGenerateContent - При включении аутентификации,
x-goog-api-key— это заголовок запроса, который прокси явно поддерживает
Предпросмотр следующего урока
В следующем уроке мы изучим Генерация изображений Imagen 3: Автоматическое сопоставление параметров size/quality OpenAI Images.
Приложение: Справочник по исходному коду
Нажмите, чтобы раскрыть расположение исходного кода
Обновлено: 2026-01-23
| Функция | Путь к файлу | Строки |
|---|---|---|
| Регистрация маршрута Gemini (/v1beta/models/*) | src-tauri/src/proxy/server.rs | 170-181 |
Разбор идентификатора модели и маршрутизация (почему префикс models/ вызывает ошибку маршрута) | src-tauri/src/proxy/common/model_mapping.rs | 58-77 |
Разбор model:method + основная логика generate/stream | src-tauri/src/proxy/handlers/gemini.rs | 14-337 |
Логика потока SSE (пересылка [DONE], а не автоматическая компенсация) | src-tauri/src/proxy/handlers/gemini.rs | 161-183 |
Структура возврата /v1beta/models (маскировка динамического списка моделей) | src-tauri/src/proxy/handlers/gemini.rs | 39-71 |
| --- | --- | --- |
| --- | --- | --- |
Пример Python SDK Google (api_endpoint указывает на локальный шлюз) | src/pages/ApiProxy.tsx | 692-734 |
| Отпечаток сессии Gemini (липкость/кэш использует session_id) | src-tauri/src/proxy/session_manager.rs | 121-158 |
| Обёртка запроса v1internal Gemini (вставка project/requestId/requestType и т.д.) | src-tauri/src/proxy/mappers/gemini/wrapper.rs | 5-160 |
| Конечная точка вышестоящего уровня v1internal и fallback | src-tauri/src/proxy/upstream/client.rs | 8-182 |
Ключевые константы:
MAX_RETRY_ATTEMPTS = 3: максимальное количество ротаций для запросов Gemini (Исходный код:src-tauri/src/proxy/handlers/gemini.rs)