Repository navigation
fix: avoid non-string enums in Gemini tool schemas - #10635
Merged
Merged
Conversation
google.genai's Schema.enum only accepts strings, and the Gemini API only
allows enum on STRING fields ("enum: only allowed for STRING type"). A
tool parameter like {"type": "integer", "enum": [10, 20, 50]}, which
FastMCP generates for Literal[10, 20, 50], made building the request fail
with a ValidationError before anything was sent.
String fields keep their enum. Other fields keep their type, so the model
still sends e.g. an integer, and list the allowed values in the
description instead. A null value is marked as nullable.
Fixes AstrBotDevs#10634
Contributor
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="astrbot/core/agent/tool.py" line_range="320-324" />
<code_context>
+ v if isinstance(v, str) else json.dumps(v) for v in values
+ ]
+ else:
+ allowed = ", ".join(json.dumps(v) for v in values)
+ description = result.get("description", "")
+ result["description"] = (
+ f"{description}\nAllowed values: {allowed}".strip()
+ )
+
if "properties" in schema:
</code_context>
<issue_to_address>
**Out-of-range values reach tools**
When gemini returns a value outside a non-string enum, the tool loop filters only unknown argument names and does not validate values against the original schema, so it passes an out-of-enum value to the handler or MCP server; callers then get a tool error or the tool acts on an unintended value.
Validate tool arguments against the original parameter schema before dispatch and return validation errors to the model for correction.
</issue_to_address>Sourcery assessment
Approval pending. 1 finding to address first.
Blocking findings: astrbot/core/agent/tool.py:324
Soulter
approved these changes
Oct 21, 2026
10 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #10634.
With the native Gemini provider, a tool parameter like
{"type": "integer", "enum": [10, 20, 50]}makes every request fail before it is sent. FastMCP generates exactly this forLiteral[10, 20, 50].ToolSet.google_schema()copiesenumas is, butgoogle.genai.types.Schema.enumislist[str], so buildingtypes.Tool(...)in_prepare_query_config()raises a pydanticValidationError. That isn't anAPIError, so the request ends with "All chat models failed", and none of the other tools can be used either.Modifications / 改动点
astrbot/core/agent/tool.py, in the Gemini schema conversion:enum.typeand dropenum, and the allowed values are added to the description (Allowed values: 10, 20, 50). Turning the values into strings isn't enough, because the Gemini API only allowsenumon STRING fields and answers 400enum: only allowed for STRING typeotherwise. Turning the field into a string doesn't work either: the model would send"20", which tools expecting an integer reject (FastMCP'sLiteral[10, 20, 50]does). Keeping the type means the model still sends an integer, and the tool's own validation still applies.nullin the enum is left out and expressed withnullable: true.json.dumps, soTrueshows up astrue. The tool's own schema isn't modified.Tests in
tests/unit/test_tool_google_schema.py:test_google_schema_keeps_enums_only_on_string_fieldscovers an integer enum with a description, a boolean enum without one and a nullable string enum, checks that the original parameters are untouched, and builds agoogle.genai.types.Toolfrom the result, which is the step that failed before.Not included: parameters that only have
$refare still converted totype: null. Fixing that means resolving$defs, which is a separate problem.This is NOT a breaking change. / 这不是一个破坏性变更。
Screenshots or Test Results / 运行截图或测试结果
Verification steps:
googlegenai_chat_completion).Literal[10, 20, 50]parameter.I don't have a Gemini key here, so the provider's
api_basepoints to a scripted Gemini endpoint. It prints the declaration it receives and, like the real API, answers 400 when a non-STRING field has anenum. The MCP tool is a small FastMCP server, and a OneBot v11 client stands in for NapCat; the rest is a normalpython main.pyinstance. The scripts and the log before the fix are in the zip attached to #10634. The full log after the fix:gemini_enum_live_after.log
Before, on master @ fc6b326. The request fails locally and nothing reaches the endpoint:
After. The endpoint gets an integer parameter with the allowed values in its description, and the MCP tool receives an
int:Tests (Windows 11, Python 3.13):
With the change to
tool.pyreverted, the new test fails: the integer field still has itsenumand no allowed values in the description.About the deselected tests and CI: the pytest jobs on current master time out after 30 minutes. Since #10614,
restart()awaitsasyncio.to_thread(close_trace_store), andtest_restart_terminates_managers_and_starts_threadpatchesthreading.Thread, so the executor can't start a worker andrestart()never returns. #10617 fixes that test, so the pytest jobs here will likely be cancelled the same way until it's merged.test_config_update_revokes_only_affected_shell_sessionshangs on my machine on master as well and passes in CI. The 4 errors are the long-prompt cases intests/unit/test_trace_store.py(also from #10614): their parameter IDs exceed Windows' 32767-character limit for environment variables. They error the same way on master, and #10617 shortens those IDs too.Checklist / 检查清单
😊 No new feature is added in this PR; it is a bug fix for [Bug] Gemini 原生提供商:工具参数含整数 enum(如 MCP 的 Literal[10, 20, 50])时所有请求在本地报 ValidationError #10634.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。
📚 No WebUI navigation, page structure, or terminology changed, so no update to
docs/zh/docs/enis needed./ 我已对照变化后的 WebUI 入口、页面结构和术语,核对并在本 PR 中更新
docs/zh和docs/en的相关操作说明与截图(或说明无需更新文档的原因)。入口改名、移动或合并时,已在文档和 changelog 中补充 旧入口 → 新入口 对照。🤓 No new dependencies are introduced.
/ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt和pyproject.toml文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。
Summary by Sourcery
Fix Gemini tool schema conversion so non-string enums no longer prevent requests from being built or sent.
Bug Fixes:
Tests: