fix: handle tool calls gracefully when tools are unavailable#7598
fix: handle tool calls gracefully when tools are unavailable#7598LovieCode wants to merge 1 commit intoAstrBotDevs:masterfrom
Conversation
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- When tools are unavailable or not found, you currently append an empty
{}forargs_ls; consider preserving the originaltool_callarguments (or a minimal subset) so downstream handlers can generate more informative error messages instead of losing context. - The new branch that handles
tools is None or not tools.func_liststill setsllm_response.role = "tool"; double-check whether downstream consumers expect actual executable tools in this role and whether a distinct role or flag would make this state easier to differentiate from a normal tool-call response.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- When tools are unavailable or not found, you currently append an empty `{}` for `args_ls`; consider preserving the original `tool_call` arguments (or a minimal subset) so downstream handlers can generate more informative error messages instead of losing context.
- The new branch that handles `tools is None or not tools.func_list` still sets `llm_response.role = "tool"`; double-check whether downstream consumers expect actual executable tools in this role and whether a distinct role or flag would make this state easier to differentiate from a normal tool-call response.
## Individual Comments
### Comment 1
<location path="astrbot/core/provider/sources/openai_source.py" line_range="855-857" />
<code_context>
- raise Exception("工具集未提供")
+
+ # 安全获取工具名称
+ tool_name = getattr(
+ getattr(tool_call, 'function', None), 'name',
+ getattr(tool_call, 'name', None)
+ )
+ if not tool_name:
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Broaden tool_name extraction to handle dict-like tool_call objects.
Because of the earlier `json.loads(tool_call)` workaround, `tool_call` may be a dict-like object. In that case, `getattr` won’t see values like `tool_call["function"]["name"]` or `tool_call["name"]`, so `tool_name` stays empty and a valid call is skipped. Please extend this logic to also handle mapping types (e.g., `isinstance(tool_call, Mapping)` and read from keys) before falling back to `getattr`.
Suggested implementation:
```python
import json
from collections.abc import Mapping
```
```python
# 安全获取工具名称,兼容对象属性和 Mapping 类型
tool_name = None
if isinstance(tool_call, Mapping):
function_part = tool_call.get("function")
if isinstance(function_part, Mapping):
tool_name = function_part.get("name")
if not tool_name:
tool_name = tool_call.get("name")
else:
tool_name = getattr(
getattr(tool_call, "function", None),
"name",
getattr(tool_call, "name", None),
)
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Code Review
This pull request modifies the OpenAI completion parsing logic to handle tool calls more gracefully when the toolset is missing or a specific tool is not found. While these changes improve error handling and logging, the implementation incorrectly uses getattr to extract information from tool_call objects. Since an existing workaround can transform these objects into dictionaries, the use of getattr will fail to retrieve the tool name and ID. Feedback has been provided to ensure compatibility with both dictionary and object types.
| tool_name = getattr( | ||
| getattr(tool_call, 'function', None), 'name', | ||
| getattr(tool_call, 'name', None) | ||
| ) | ||
| if not tool_name: | ||
| logger.warning(f"Could not extract tool name from tool_call: {tool_call}") | ||
| continue | ||
|
|
||
| if tools is None or not tools.func_list: | ||
| logger.warning(f"LLM requested tool '{tool_name}' but no tools are available") | ||
| func_name_ls.append(tool_name) | ||
| tool_call_ids.append(getattr(tool_call, 'id', '')) | ||
| args_ls.append({}) | ||
| continue |
There was a problem hiding this comment.
getattr 函数无法在字典(dict)上工作。由于第 852 行的 workaround 会将字符串形式的 tool_call 解析为字典,这里的 getattr(tool_call, ...) 将无法获取到任何内容,导致 tool_name 为 None 并在第 861 行跳过该工具调用。此外,第 866 行的 getattr(tool_call, 'id', '') 在处理字典时也会返回空字符串。建议根据 tool_call 的类型(字典或对象)分别处理,以确保兼容性。
# 安全获取工具名称和 ID
if isinstance(tool_call, dict):
tool_name = tool_call.get('function', {}).get('name') or tool_call.get('name')
tool_id = tool_call.get('id', '')
else:
tool_name = getattr(getattr(tool_call, 'function', None), 'name', getattr(tool_call, 'name', None))
tool_id = getattr(tool_call, 'id', '')
if not tool_name:
logger.warning(f"Could not extract tool name from tool_call: {tool_call}")
continue
if tools is None or not tools.func_list:
logger.warning(f"LLM requested tool '{tool_name}' but no tools are available")
func_name_ls.append(tool_name)
tool_call_ids.append(tool_id)
args_ls.append({})
continue| else: | ||
| logger.warning(f"Tool '{tool_name}' not found in toolset") | ||
| func_name_ls.append(tool_name) | ||
| tool_call_ids.append(getattr(tool_call, 'id', '')) |
4da358f to
3c9d4c2
Compare
3c9d4c2 to
1c5135d
Compare
There was a problem hiding this comment.
我觉得更好的做法是在 tool_loop_agent_runner 去做检测
问题描述:
当 LLM 返回 tool_calls 但工具列表为空(
tools is None或tools.func_list为空)时,原代码会将 Agent 状态异常转换为 DONE,直接结束对话,不输出任何返回。修复方案:
修改
openai_source.py中的工具调用解析逻辑,当工具列表为空或工具不存在时:Modifications / 改动点
修改了
astrbot/core/provider/sources/openai_source.py在
_parse_openai_completion方法中增加了对工具列表为空和工具不存在情况的处理使用
getattr安全获取工具名称,兼容不同格式的 tool_callThis is NOT a breaking change. / 这不是一个破坏性变更。
Screenshots or Test Results / 运行截图或测试结果
修改前:

修改后的日志输出:

Agent 状态不再异常转为 DONE,而是继续处理并返回工具不存在的提示。
Checklist / 检查清单
😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了"验证步骤"和"运行截图"。
🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in
requirements.txtandpyproject.toml./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt和pyproject.toml文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。
Summary by Sourcery
Handle LLM tool calls gracefully when requested tools are missing or unavailable instead of terminating the agent flow.
Bug Fixes: