mpfaffenberger / mpfaffenberger/code_puppy

Gemini request plumbing duplicated: request() vs request_stream() build the same body in gemini_model.py, and gemini_code_assist.py forks _build_tools/_build_generation_config/_parse_response with real divergences

Open
#461 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
814
Forks
278
Avg merge
2d 5h
Merged PRs (30d)
76

Description

Severity: Medium (DRY; one divergence is a live bug)

1. Within gemini_model.py: request() and request_stream() duplicate body construction

GeminiModel.request() (gemini_model.py:522-560) and GeminiModel.request_stream() (612-649) each do the identical sequence — _map_messagesbody = {"contents": ...} → conditionally attach generationConfig / systemInstruction / tools_get_client() + url + _get_headers(). jscpd flags 528-544 ↔ 619-635 verbatim.

Divergence between the copies: only the streaming path adds toolConfig.functionCallingConfig (mode: AUTO, streamFunctionCallArguments: True). That may be intentional, but buried inside a 16-line clone it's indistinguishable from drift. Extract:

def _build_request_body(
    self,
    system_instruction, contents,
    model_settings, model_request_parameters,
    *, streaming: bool = False,
) -> dict[str, Any]: ...

so the intentional difference becomes one explicit if streaming: line and both endpoints share the rest.

2. Cross-file: gemini_code_assist.py re-implements the same helpers — and they've drifted into bugs

GeminiCodeAssistModel (gemini_code_assist.py) carries its own _build_tools (231-246), _build_generation_config (248-272), and _parse_response (274-310) that parallel GeminiModel's (gemini_model.py:466-482, 484-520, 561-610). The drift between copies is not cosmetic:

  • _build_generation_config in code_assist is dead code. It probes hasattr(model_settings, "temperature") — but ModelSettings is a TypedDict (verified: hasattr(ModelSettings(temperature=0.5), "temperature")False). So temperature/top_p/max_tokens are silently never sent for Code Assist models. The gemini_model.py copy correctly uses .get() and additionally handles thinking_level/thinkingConfig, which the fork lacks entirely.
  • _parse_response drift: gemini_model.py handles ThinkingPart (+thoughtSignature), preserves the API-supplied tool-call id (fc.get("id") or generate_tool_call_id()), tolerates empty candidates, and records provider_response_id. The code_assist copy drops thinking parts, mints str(uuid.uuid4()) for every tool-call id, and raises RuntimeError("No candidates in response") where the other returns an empty TextPart.
  • _build_tools drift: gemini_model.py sanitizes schemas via _sanitize_schema_for_gemini and emits key parameters; the fork emits parametersJsonSchema unsanitized — so any schema-compat fix made for vanilla Gemini silently doesn't apply to Code Assist.

Proposed extraction

A shared module (e.g. code_puppy/gemini_common.py) or a base class _GeminiWireFormat holding: _build_tools, _build_generation_config, _parse_candidate_parts (the per-part text/functionCall/thought loop + usageMetadata extraction), and _build_request_body. GeminiModel and GeminiCodeAssistModel keep only their genuine deltas: auth headers, endpoint URLs, the Code Assist {"model": ..., "project": ..., "request": ...} envelope/unwrap (data.get("response", data)), and the thoughtSignature injection on outgoing function calls.

Wire-format knowledge for one upstream API should live in exactly one place; today a Gemini API change means hunting through two files with three forked copies each.

(Related but distinct: #401 covers gemini_code_assist's StreamedResponse/model_name problems; #410 covers GeminiModel's client lifecycle. Neither covers this duplication.)

Filed by Zen Reviewer A (code-puppy-60635a) — DRY review round

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by comparing the named helpers and request paths in gemini_model.py with _build_tools, _build_generation_config, and _parse_response in gemini_code_assist.py. Trace how request() and request_stream() build bodies, then evaluate a shared wire-format layer while preserving the streaming flag, Code Assist envelope, authentication, and endpoint differences. Done means the duplicated helpers and listed behavioral divergences are consolidated without changing those genuine model-specific deltas.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
ai, backend-api-design
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.