Skip to content

feat(agent): GeminiProvider over reqwest with function calling (#9) - #57

Merged
ammar-s847 merged 1 commit into
mainfrom
agent/gemini-provider
May 27, 2026
Merged

feat(agent): GeminiProvider over reqwest with function calling (#9)#57
ammar-s847 merged 1 commit into
mainfrom
agent/gemini-provider

Conversation

@ammar-s847

Copy link
Copy Markdown
Contributor

Implements #9 — the first concrete LlmProvider. Stacked on #8 (PR #49); base is agent/loop-provider, so review/merge #49 first.

What

  • core/src/agent/provider/gemini.rs: GeminiProvider against :generateContent, no SDK (mirrors chat/anthropic.rs).
  • Pure conversions to_wire_request / from_wire_response (unit-tested without a network); complete() is the thin HTTP shell.
  • System → systemInstruction; user/assistant → user/model; assistant tool_callsfunctionCall; tool results → functionResponse (role user), routed by name.
  • API key via x-goog-api-key header (never in URL/query/logs).
  • HTTP errors classified (400→InvalidRequest, 401/403→Auth, else→Provider).
  • Small neutral-type addition: ChatMessage.name so tool results carry the tool name (Gemini routes by name; OpenAI uses it too). tool_result(id, name, content).

MVP / Acceptance (#9)

  • maps Gemini functionCall/functionResponse ↔ neutral types
  • model configurable (request-driven; verified gemini-2.5-flash)
  • API key from request, never logged
  • returns token usage
  • integration test parses text + a tool call

Live verification (real key)

Ran the #[ignore] smoke test against gemini-2.5-flash:

TEXT -> "pong" | usage=TokenUsage { prompt_tokens: 8, completion_tokens: 1, total_tokens: 9 }
TOOLCALLS -> [ToolCall { name: "apply_seed", arguments: {"kind":"small-world","n":200} }] | finish=ToolCalls

Note: gemini-2.0-flash returned HTTP 429 limit: 0 (no free-tier quota on this key) — our error classification handled it correctly; default test model is now 2.5-flash.

Tests

cargo test -p core agent::provider → 13 passed, 1 ignored (live).
Live: GOOGLE_API_KEY=... cargo test -p core gemini_live -- --ignored --nocapture.

Closes #9.

Base automatically changed from agent/loop-provider to main May 27, 2026 22:13
Implements LlmProvider for Gemini against :generateContent (no SDK,
same posture as chat/anthropic.rs):

- Pure, unit-tested conversions to_wire_request / from_wire_response;
  complete() is the thin HTTP shell around them.
- System turns lifted to systemInstruction; user/assistant -> user/model;
  assistant tool_calls -> functionCall parts; tool results -> functionResponse
  parts (role user), routed by name.
- API key sent via x-goog-api-key header (never in URL/query/logs).
- Token usage reported; finish_reason forced to ToolCalls when the model
  emits function calls.
- HTTP errors classified (400 InvalidRequest, 401/403 Auth, else Provider).
- with_base_url() for mock-server tests.

Adds ChatMessage.name (Option) so tool results carry the tool name for
name-routed providers; tool_result() now takes (id, name, content).

Tests: 7 non-live (wire mapping + parsing + error classification) plus an
#[ignore] live smoke test. Verified live against gemini-2.5-flash with a
real key: text completion returned usage, tool calling returned
apply_seed{kind,n} with finish=ToolCalls.

Closes #9.
@ammar-s847
ammar-s847 force-pushed the agent/gemini-provider branch from c6fe269 to 459a1e9 Compare May 27, 2026 22:17
@ammar-s847
ammar-s847 merged commit f65d7c3 into main May 27, 2026
2 checks passed
@ammar-s847
ammar-s847 deleted the agent/gemini-provider branch May 27, 2026 22:20
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.

[A2] GeminiProvider over reqwest (generateContent + function calling)

1 participant