Skip to content

Gemini Native API Adapter#227

Open
daniyalmaroufi wants to merge 3 commits intoaiming-lab:mainfrom
daniyalmaroufi:main
Open

Gemini Native API Adapter#227
daniyalmaroufi wants to merge 3 commits intoaiming-lab:mainfrom
daniyalmaroufi:main

Conversation

@daniyalmaroufi
Copy link
Copy Markdown

This PR introduces native support for Google's Gemini API endpoints.

Features

  • Gemini Native API Adapter
  • Introduced a dedicated GeminiAdapter (researchclaw/llm/gemini_adapter.py) enabling AutoResearchClaw to communicate directly via the native REST :generateContent Google API.
  • Implemented payload translation moving from standard messages structures into Google's {"contents": [{"parts": ...}]} schema.
  • Added native handler for top-level systemInstruction, strict response formatting, and model message alternation logic.
  • Registered "gemini" into the provider presets alongside "openai-compatible", allowing for frictionless setup within config.arc.yaml.

Copilot AI review requested due to automatic review settings April 7, 2026 20:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds first-class support for Google’s Gemini native :generateContent REST API by introducing a dedicated adapter and wiring it into LLMClient via provider presets.

Changes:

  • Added GeminiAdapter to translate OpenAI-style {role, content} messages into Gemini’s contents/parts schema and map responses back.
  • Registered a new "gemini" provider preset and initialized/used the adapter in LLMClient.from_rc_config() / _raw_call().
  • Adjusted JSON-mode compatibility handling in the OpenAI-compatible path (adds "apple" to the no-response_format list).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
researchclaw/llm/gemini_adapter.py New Gemini native adapter implementing request/response translation and JSON-mode support.
researchclaw/llm/client.py Wires the Gemini adapter into client initialization and request dispatch.
researchclaw/llm/init.py Adds the "gemini" provider preset and documents it in the factory docstring.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +105 to +115
try:
with urllib.request.urlopen(req, timeout=self.timeout_sec) as resp:
data = json.loads(resp.read())
except urllib.error.HTTPError as exc:
# Attempt to extract detailed error from Gemini to aid debugging
try:
body_err = exc.read().decode("utf-8")
logger.error("Gemini API Error: %s", body_err)
except Exception: # noqa: BLE001
pass
raise
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the HTTPError path, the adapter calls exc.read() to log the error body and then re-raises the same HTTPError. This consumes the underlying response stream, so LLMClient._call_with_retry will usually see an empty body on e.read(), which can break retry/transient-400 detection and the 403 “model not allowed” fallback logic. Capture the error body bytes once and re-raise a new urllib.error.HTTPError (or reset exc.fp) with an in-memory buffer so upstream can still read the body; also consider truncating what gets logged.

Copilot uses AI. Check for mistakes.
Comment on lines +127 to +131
gemini_finish = first_candidate.get("finishReason", "STOP")
if gemini_finish == "MAX_TOKENS":
finish_reason = "length"
else:
finish_reason = "stop"
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

finishReason mapping currently only distinguishes MAX_TOKENS vs everything else. Gemini also returns other reasons (e.g., SAFETY / RECITATION) that should not be reported as "stop" because downstream may treat this as a normal completion. Map these to a more appropriate OpenAI-style finish_reason (e.g., "content_filter") or surface the raw reason in a structured way.

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +37
"gemini": {
"base_url": "https://generativelanguage.googleapis.com/v1beta",
},
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a new provider preset + adapter path should come with unit coverage similar to tests/test_minimax_provider.py (preset registration, factory wiring, and a stubbed urlopen round-trip for GeminiAdapter). Without this, regressions in payload translation / URL construction may go unnoticed.

Copilot uses AI. Check for mistakes.
Comment on lines +176 to +181
elif provider == "gemini":
from .gemini_adapter import GeminiAdapter

client._gemini = GeminiAdapter(
original_base_url, original_api_key, config.timeout_sec
)
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LLMClient.preflight()’s 404 message uses self._endpoint_url(self.config.base_url), which is specific to OpenAI-compatible endpoints (/chat/completions or /responses). With the new Gemini provider, a 404 from GeminiAdapter will be reported with an incorrect endpoint URL, making debugging harder. Consider branching preflight error messaging when a native adapter (Gemini/Anthropic) is active to report the actual request URL or a provider-specific hint.

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants