Skip to content

fix: BUG-016 Agentic loop 轮数耗尽后无输出(safety net 增强 + fallback)#2

Open
xiaoshuai155vv wants to merge 1 commit intomainfrom
bugfix/016-agentic-loop-output
Open

fix: BUG-016 Agentic loop 轮数耗尽后无输出(safety net 增强 + fallback)#2
xiaoshuai155vv wants to merge 1 commit intomainfrom
bugfix/016-agentic-loop-output

Conversation

@xiaoshuai155vv
Copy link
Collaborator

  • 扩展 safety net 触发条件:轮数耗尽或有工具调用但无输出时强制执行
  • 增强 summary prompt:包含工具调用摘要 + 明确指令 + fallback 选项
  • 添加 fallback 机制:基于 allToolCalls 生成合成回复,确保用户收到结果

🤖 Generated with Claude Code

- 扩展 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>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @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

  • 增强 Safety Net 触发条件: 扩展了 AI Agentic loop 的安全网触发条件,使其不仅在轮数耗尽时触发,还在 AI 有工具调用但未能生成文本输出时强制执行总结。
  • 优化总结提示词: 增强了 AI 总结轮次的提示词,使其包含工具调用摘要、明确的指令以及在无法总结时的回退选项,以提高总结的质量和可靠性。
  • 引入回退机制: 新增了一个回退机制,当安全网总结轮次仍未能生成内容时,系统会基于所有工具调用记录生成一个合成回复,确保用户始终能收到结果。
  • BUG-016 状态更新: 将 docs/buglist.md 中 BUG-016 的状态从“挂起”更新为“已修复”,并更新了其描述和整体 bug 统计。
Changelog
  • docs/buglist.md
    • 更新了 BUG-016 的状态为“已修复”。
    • 修订了 BUG-016 的描述,以反映安全网增强和回退机制的实现。
    • 调整了 bug 统计,将“已修复”数量增加,将“挂起”数量清零。
  • web-ide/backend/src/main/kotlin/com/forge/webide/service/ClaudeAgentService.kt
    • 修改了安全网的触发逻辑,使其在 AI 未生成文本输出且有工具调用时,或达到最大轮数时触发。
    • 更新了 AI 总结轮次的用户消息,以使用新的 buildSummaryPrompt 函数生成的增强提示词。
    • 添加了在总结轮次仍无内容时,通过 generateFallbackFromToolCalls 函数生成回退响应的逻辑。
    • 新增了私有函数 buildSummaryPrompt,用于构建包含工具调用摘要和明确指令的总结提示词。
    • 新增了私有函数 generateFallbackFromToolCalls,用于在所有其他尝试失败时,基于工具调用结果生成合成响应。
Activity
  • 自创建以来,此拉取请求尚未有任何人工活动(如评论或审查)。
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

这里存在一个严重的编译错误:变量 turn 是在 for 循环内部定义的(第 255 行),在循环外部无法访问。此外,由于循环范围是 1..MAX_AGENTIC_TURNS,条件 turn > MAX_AGENTIC_TURNS 在逻辑上也是永远不会成立的。建议在循环外部定义一个变量来记录是否达到了最大轮数,或者检查最后的 stopReason 是否仍为 TOOL_USE

Comment on lines +605 to +621
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()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

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.

Comment on lines +634 to +641
val toolSummaries = toolCalls.map { call ->
val status = when (call.status) {
"complete" -> "成功"
"error" -> "失败"
else -> call.status
}
"- ${call.name}: $status"
}.joinToString("\n")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

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("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&#39;")
            "- ${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"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

在这里发送 complete 状态事件可能过早。Safety net 触发后,系统还会进行一轮总结(Summary turn),此时任务并未真正完成。streamMessage 方法(第 208 行)在 agenticStream 返回后会统一发送 complete 事件,建议移除此处的冗余调用,以免 UI 提前进入完成状态导致显示异常。

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.

1 participant