DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

AutoGen 工具已执行,反思阶段却报 no valid text response

AutoGen 0.6.4 工具反思返回 FunctionCall 列表时,区分执行结果、tool_choice 兼容性与有限工具迭代,并核对最终消息类型和重复操作风险。

工具已经返回结果,Agent 随后却抛出:

RuntimeError: Reflect on tool use produced no valid text response.

这时应检查失败的是哪一次模型调用。工具执行后的反思请求如果又返回 FunctionCall 列表,而不是文字,工具本身成功也无法让这一步通过。

以下按 Python AutoGen 的 autogen-agentchat 0.6.4 说明,关注 AssistantAgent、reflect_on_tool_use=True 和 OpenAI 兼容模型客户端的组合。不能仅凭模型名称把所有同类异常归到这一原因;需要核对实际端点、客户端返回对象及完整堆栈。

工具结果与反思结果是两次不同的输出

这一版 Agent 会先执行模型请求的工具调用,将结果加入上下文。开启反思后,再基于这些结果调用模型,要求生成最终回复;这次请求设置了 tool_choice="none"。

反思代码随后要求 reflection_result.content 是字符串。如果收到的是工具调用列表,就会抛出上面的异常。即使响应的 thought 字段中已经写了“第一步完成,继续第二步”,也不能把它当作最终文字输出;判断的是 content,不是有没有任何看起来像文字的字段。

可以按调用顺序对照受控日志:工具执行结果的 is_error、反思请求的 tool_choice、反思响应的 finish_reason 和 content 类型。不要把工具入参、业务结果或密钥原样输出到公共日志。若工具结果本身是错误,或模型响应为空,应分别处理,不能看到同一条反思异常就假定工具成功、端点一定忽略了参数。

Gemini、Grok 等模型经兼容端点接入时,也需要核对该端点对 tool_choice="none" 的实际支持。客户端接受配置只说明请求可以构造,不证明服务端遵守了它。将工具调用列表转成字符串以绕过类型检查,会把尚待执行的动作伪装成回复,也可能破坏后续上下文,不应直接修改这条检查。

还要继续用工具时,走有上限的工具迭代

如果业务本来需要“取得一个结果,再决定下一次工具调用”,可以评估关闭反思,让正常工具迭代承担这个流程。在 0.6.4 中,max_tool_iterations 控制单次运行的工具迭代上限;模型返回文字时可以提前结束,达到上限也会结束工具循环。

下面是一个完整的异步入口函数,接收应用已有的 ChatCompletionClient。客户端需按实际端点配置并支持工具调用;示例没有内置模型地址、密钥或自动运行入口。工具只从示例字典中读取状态,适合先检查输出类型和调用次数,不承担写操作:

from autogen_core.models import ChatCompletionClient
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.messages import TextMessage, ToolCallSummaryMessage

def read_status(item: str) -> str:
    """Read one item from a fixed demonstration status table."""
    statuses = {"A": "ready", "B": "waiting"}
    if item not in statuses:
        raise ValueError("Unknown demonstration item")
    return statuses[item]

async def run_status_check(model_client: ChatCompletionClient):
    agent = AssistantAgent(
        name="status_reader",
        model_client=model_client,
        tools=[read_status],
        reflect_on_tool_use=False,
        max_tool_iterations=3,
        system_message=(
            "Read the status of A and B with read_status. "
            "Once both are known, respond with a short factual summary. "
            "Do not repeat a status lookup that already succeeded."
        ),
    )
    result = await agent.run(task="查询 A 和 B 的状态")
    final = result.messages[-1]
    if isinstance(final, ToolCallSummaryMessage):
        for execution in final.results:
            if execution.is_error:
                raise RuntimeError("A demonstration tool call failed")
        return {"kind": "tool_summary", "content": final.content}
    if isinstance(final, TextMessage):
        return {"kind": "text", "content": final.content}
    raise TypeError("Unexpected final message type")

3 是示例的有限上限,不代表任务一定能在三轮内完成。一次模型回复也可能提出多个工具调用;因此迭代上限不是工具总调用次数上限。关闭反思后,达到上限时可能返回 ToolCallSummaryMessage,其摘要对应最后一轮工具结果,不应当作完整任务的最终结论。上面的分支只是区分返回类型和该摘要中的错误,应用仍需从执行记录核对 A、B 是否都取得了结果,以及是否有重复调用或更早的失败。

客户端由调用方管理,这个入口没有关闭共享客户端;应用应在它自己的生命周期结束时释放连接。每次创建新的 Agent 也会使用新的会话上下文。若业务要延续同一会话,应另行管理已有状态,不能为消除反思报错就悄悄改变对话隔离方式。

必须要最终文字时,保留这项输出要求

关闭反思会改变回复路径。如果下游必须拿到对工具结果的文字说明或结构化回复,就不能只把开关关掉并继续按原格式读取。可以保留反思,检查端点的参数兼容性,或在应用中明确安排一个不再暴露工具的结果总结步骤,同时验证它的输出。

增加工具迭代也不会修复一个始终忽略 tool_choice 的端点。更换客户端、模型或版本前,应按同一任务检查工具是否只做了允许的动作、最终回复类型是否合约一致,以及失败是否能在有限次数内退出。没有这些结果,不能承诺升级会解决这条报错。

若工具包含付款、发消息或写数据,反思失败后重跑整项任务可能重复操作。恢复运行前先确认已经完成的动作和业务标识;只有可安全重试的部分才重新执行。撤销配置调整时,也要一并恢复接收端对消息类型的处理,避免把新的工具摘要交给只接受文字回复的旧逻辑。