Repository navigation
Python: Reject candidate_count>1 in the Gemini chat client instead of silently dropping candidates - #9929
Conversation
… silently dropping candidates Both the streaming and non-streaming parsers read candidates[0] only. candidate_count flows to the API through the generic option passthrough, so a multi-candidate request is billed for every candidate while all but the first are silently discarded, and finish reasons and usage are taken from candidate 0 alone. Reject the option up front, mirroring the Ollama client's explicit rejection of unsupported tool_choice modes. 🤖 Generated with ZCode Co-Authored-By: ZCode <noreply@zcode.dev>
db38dfa to
b7884ad
Compare
|
The initial push of this branch was mis-based (it accidentally contained an unrelated worktree snapshot) and was force-pushed to a single clean commit (c284568, gemini files only). The Copilot review above was submitted against that stale commit — its summary ("shell API change", "Redis migration test") references files that are not part of this PR. The current diff is exactly two gemini files (+32 lines). |
There was a problem hiding this comment.
🔵 Needs a closer look
The shell API change breaks existing callers, and the Redis migration test leaves a test-owned key behind during cleanup.
No open issues 🎉
What changed in this PR
This PR addresses #9928 by rejecting Gemini requests for multiple candidates rather than silently discarding responses. It also includes broader changes to Python Agent Framework context management, resource limits, and DevUI file handling.
Changes:
- Reject unsupported Gemini candidate counts and preserve both MCP approval lists in OpenAI and Foundry clients.
- Add experimental agent-triggered compaction, skill unloading, and identity-preserving Redis scope filters.
- Bound shell output and Monty attachment capture, and restrict DevUI file URLs, with supporting tests and documentation.
| File | Description |
|---|---|
python/packages/tools/tests/test_shell_truncate_and_quote.py |
Test bounded streaming capture and decoding. |
python/packages/tools/tests/test_shell_parse_rc.py |
Test incremental completion-marker parsing. |
python/packages/tools/tests/test_local_shell_tool.py |
Cover output limits, cancellation, and pipe cleanup. |
python/packages/tools/tests/test_docker_shell_tool.py |
Cover bounded Docker output and termination cleanup. |
python/packages/tools/README.md |
Document independent output limits. |
python/packages/tools/agent_framework_tools/shell/_types.py |
Update truncation field documentation. |
python/packages/tools/agent_framework_tools/shell/_truncate.py |
Add bounded byte and text buffers. |
python/packages/tools/agent_framework_tools/shell/_tool.py |
Expose separate stdout and stderr limits. |
python/packages/tools/agent_framework_tools/shell/_session.py |
Bound persistent capture and parse markers incrementally. |
python/packages/tools/agent_framework_tools/shell/_executor.py |
Use concurrent bounded stateless capture. |
python/packages/tools/agent_framework_tools/shell/_docker.py |
Apply per-stream limits and revise cleanup. |
python/packages/tools/agent_framework_tools/shell/_capture.py |
Add shared subprocess capture and pipe cleanup. |
python/packages/redis/tests/test_providers.py |
Update scope-schema expectations and mocks. |
python/packages/redis/tests/test_context_provider_sdk.py |
Verify scope tokens and client ownership. |
python/packages/redis/tests/test_context_provider_integration.py |
Add live isolation and migration tests. |
python/packages/redis/tests/test_context_provider_edges.py |
Cover scope encoding and schema validation. |
python/packages/redis/README.md |
Document incompatible-schema migration. |
python/packages/redis/agent_framework_redis/_context_provider.py |
Add hashed scope filters and compatibility checks. |
python/packages/openai/tests/openai/test_openai_chat_client.py |
Test continuation rejection and combined approval lists. |
python/packages/openai/agent_framework_openai/_chat_client.py |
Declare continuation aliases and merge approval lists. |
python/packages/monty/tests/monty/test_monty_codeact.py |
Test attachment budgets and bounded reads. |
python/packages/monty/README.md |
Document per-execution capture limits. |
python/packages/monty/agent_framework_monty/_instructions.py |
Describe capture limits in agent instructions. |
python/packages/monty/agent_framework_monty/_execute_code_tool.py |
Enforce attachment count and byte budgets. |
python/packages/gemini/tests/test_gemini_client.py |
Test candidate-count rejection and defaults. |
python/packages/gemini/agent_framework_gemini/_chat_client.py |
Reject requests for multiple candidates. |
python/packages/foundry/tests/foundry/test_foundry_chat_client.py |
Verify both MCP approval lists survive. |
python/packages/foundry/agent_framework_foundry/_chat_client.py |
Merge MCP approval lists without overwriting. |
python/packages/devui/frontend/yarn.lock |
Lock added browser-test dependencies. |
python/packages/devui/frontend/test/file-urls.test.mjs |
Test URL restrictions and embedded-file lifecycle. |
python/packages/devui/frontend/src/lib/file-urls.ts |
Validate file URLs and embedded formats. |
python/packages/devui/frontend/src/components/features/agent/message-renderers/OpenAIContentRenderer.tsx |
Use validated links and managed blob URLs. |
python/packages/devui/frontend/package.json |
Add file-URL tests and Happy DOM. |
python/packages/devui/frontend/package-lock.json |
Lock updated frontend dependencies. |
python/packages/core/tests/core/test_function_invocation_logic.py |
Verify rejection-result metadata. |
python/packages/core/tests/core/test_compaction.py |
Cover trigger semantics and skill unloading. |
python/packages/core/tests/core/test_clients.py |
Test client-managed-history requirements. |
python/packages/core/tests/core/test_agents.py |
Test compaction within agent execution. |
python/packages/core/AGENTS.md |
Document compaction architecture and constraints. |
python/packages/core/agent_framework/_types.py |
Define rejection and skill-provenance keys. |
python/packages/core/agent_framework/_tools.py |
Mark rejected approval results. |
python/packages/core/agent_framework/_skills.py |
Record resolved skill identity on results. |
python/packages/core/agent_framework/_feature_stage.py |
Register the experimental compaction feature. |
python/packages/core/agent_framework/_compaction.py |
Add trigger-driven compaction and skill unloading. |
python/packages/core/agent_framework/_clients.py |
Reject incompatible service-managed history options. |
python/packages/core/agent_framework/__init__.pyi |
Export new strategy types. |
python/packages/core/agent_framework/__init__.py |
Expose new strategies through lazy exports. |
Review effort: Balanced
| # unsupported-tool_choice rejection). | ||
| raise ChatClientInvalidRequestException( | ||
| "Gemini chat client supports a single candidate. " | ||
| f"candidate_count={value} would silently drop every candidate but the first; " |
There was a problem hiding this comment.
the default should be that folks remove it altogether, but if passed, it needs to be 1
| if key == "stop" and isinstance(value, str): | ||
| # ChatOptions allows a single stop string; stop_sequences takes a list. | ||
| value = [value] | ||
| if key == "candidate_count" and value > 1: |
There was a problem hiding this comment.
0 as value would also be wrong, so let's make it:
| if key == "candidate_count" and value > 1: | |
| if key == "candidate_count" and value != "1": |
Fixes #9928
Description
candidate_countflows to the Gemini API through the generic option passthrough, but both the streaming and non-streaming parsers readcandidates[0]only — socandidate_count > 1is billed for every candidate while all but the first are silently discarded, and finish reasons and usage are attributed to candidate 0 alone. Reject the option up front in_prepare_configwithChatClientInvalidRequestException, mirroring the Ollama client's explicit rejection of unsupportedtool_choicemodes.candidate_count=1and absent flow through unchanged.RegressionCheck
test_candidate_count_above_one_is_rejected:{"candidate_count": 2}raises before any request is built;{"candidate_count": 1}still forwards (config.candidate_count == 1); absent staysNone.tests/for the gemini package on this branch: failure set identical tomain(pre-existing environment failures only; zero new).🤖 Generated with ZCode
Co-Authored-By: ZCode noreply@zcode.dev