fix: BUG-016 Agentic loop 轮数耗尽后无输出(safety net 增强 + fallback)#2
fix: BUG-016 Agentic loop 轮数耗尽后无输出(safety net 增强 + fallback)#2xiaoshuai155vv wants to merge 1 commit intomainfrom
Conversation
- 扩展 safety net 触发条件:轮数耗尽或有工具调用但无输出时强制执行 - 增强 summary prompt:包含工具调用摘要 + 明确指令 + fallback 选项 - 添加 fallback 机制:基于 allToolCalls 生成合成回复,确保用户收到结果 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Summary of ChangesHello @xiaoshuai155vv, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 此拉取请求旨在解决 AI Agentic loop 在轮数耗尽或工具调用后未能生成文本输出的问题(BUG-016)。通过扩展安全网的触发条件、增强总结提示词以及引入一个健壮的回退机制,确保 AI 代理在复杂交互后始终能向用户提供有意义的响应,从而提升用户体验和系统的稳定性。 Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This PR addresses BUG-016 by improving the Agentic loop's resilience when turns are exhausted or no text is output, enhancing safety net triggers, optimizing summary prompts, and introducing a fallback mechanism. However, a security audit revealed two high-severity vulnerabilities in the new fallback and safety net logic: a Prompt Injection flaw due to unvalidated tool names from the LLM being embedded into prompts, and a Cross-Site Scripting (XSS) vulnerability where these unvalidated tool names are sent to the client in fallback messages. Both stem from improper handling of LLM-originated data. Furthermore, there's a serious compilation error (variable scope issue) and a minor flaw in the timing of OODA state event sending that need to be resolved before merging.
| if (finalContent.isBlank()) { | ||
| logger.info("All $MAX_AGENTIC_TURNS turns exhausted with tool_use. Forcing final summary turn.") | ||
| val needsSummaryTurn = finalContent.isBlank() && allToolCalls.isNotEmpty() | ||
| if (needsSummaryTurn || turn > MAX_AGENTIC_TURNS) { |
| private fun buildSummaryPrompt(toolCalls: List<ToolCallRecord>): String { | ||
| val toolNames = toolCalls.map { it.name }.distinct().joinToString(", ") | ||
| val toolCount = toolCalls.size | ||
|
|
||
| return """ | ||
| |你已经完成了 $toolCount 次工具调用,使用的工具包括:$toolNames。 | ||
| | | ||
| |请基于工具调用的结果,为用户提供一个完整、详细的回复。回复应该: | ||
| |1. 总结你通过工具发现或获取的关键信息 | ||
| |2. 直接回答用户的问题,而不是描述你做了什么 | ||
| |3. 如果信息不完整,明确说明你能确认的和不能确认的部分 | ||
| | | ||
| |如果无法基于工具调用结果给出回复,至少提供一个基于你的理解的合理回答。 | ||
| | | ||
| |请现在开始给出你的回复: | ||
| """.trimMargin() | ||
| } |
There was a problem hiding this comment.
The buildSummaryPrompt function constructs a new prompt that includes tool names originating from the LLM's output without any sanitization. This creates a prompt injection vulnerability. If an attacker can influence the LLM to generate a tool name containing malicious instructions, they can hijack the agent's behavior. To fix this, you must validate the tool names against a strict allow-list of known tools before embedding them into the prompt.
| val toolSummaries = toolCalls.map { call -> | ||
| val status = when (call.status) { | ||
| "complete" -> "成功" | ||
| "error" -> "失败" | ||
| else -> call.status | ||
| } | ||
| "- ${call.name}: $status" | ||
| }.joinToString("\n") |
There was a problem hiding this comment.
The generateFallbackFromToolCalls function includes raw tool names, which originate from the LLM, in a fallback message sent to the client. If the client application renders this message as HTML, it can lead to a Cross-Site Scripting (XSS) vulnerability. An attacker could trick the LLM into generating a tool name with a JavaScript payload, which would then execute in the user's browser. To remediate this, you must HTML-escape the tool names before including them in the response.
val toolSummaries = toolCalls.map { call ->
val status = when (call.status) {
"complete" -> "成功"
"error" -> "失败"
else -> call.status
}
val safeName = call.name
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'")
"- ${safeName}: $status"
}.joinToString("\n")| if (needsSummaryTurn || turn > MAX_AGENTIC_TURNS) { | ||
| val reason = if (finalContent.isBlank()) "AI produced no text output" else "Max turns exhausted" | ||
| logger.info("Safety net triggered: $reason. Forcing final summary turn. allToolCalls=${allToolCalls.size}") | ||
| onEvent(mapOf("type" to "ooda_phase", "phase" to "complete")) |
🤖 Generated with Claude Code