diff --git a/yuthon_ai_agent/models/ai_agent.py b/yuthon_ai_agent/models/ai_agent.py index b224e283a..02871506a 100644 --- a/yuthon_ai_agent/models/ai_agent.py +++ b/yuthon_ai_agent/models/ai_agent.py @@ -693,22 +693,31 @@ class AiConversation(models.Model): # --------------------------------------------------------- # 主入口 # --------------------------------------------------------- - def _check_hallucinated_url(self, content): + def _check_hallucinated_url(self, content, legit_urls=None): """检测并移除 LLM 响应中编造的报表链接。 真正的报表链接由 generate_report 工具返回,格式为 - https://域名/yuthon/report/<12位hex>。 + https://域名/ai/report/。 LLM 有时会跳过工具调用,直接在文本中编造一个链接, - 典型模式:/ai/report/<任意>、/report/ 等。 + 典型模式:/report/ 等。 检测到后,整条回复替换为明确的错误提示,避免误导用户。 + + :param legit_urls: 本次对话中通过 generate_report / get_report_link + 工具合法生成的 URL 列表,白名单内的 URL 不拦截。 """ if not content: return content import re - hallucinated = re.findall( + legit_urls = legit_urls or [] + # 找出所有看起来像报表链接的 URL + all_report_urls = re.findall( r'https?://[^\s]*?/(?:ai[-_]?)?report/[^\s]+', content, re.IGNORECASE ) + if not all_report_urls: + return content + # 过滤掉白名单中的合法 URL,剩下的就是编造的 + hallucinated = [u for u in all_report_urls if u not in legit_urls] if hallucinated: _logger.warning( 'LLM 编造了报表链接并已拦截 (conversation=%s, uid=%s): %s', @@ -831,6 +840,7 @@ class AiConversation(models.Model): # 3. 多轮 tool_calls 循环 final_content = '' + legit_report_urls = [] # 收集本次对话中合法生成的报表 URL for _ in range(MAX_TOOL_ITERATIONS): assistant_msg, err = self._call_chat_completion(provider, messages, tools) if err: @@ -860,6 +870,9 @@ class AiConversation(models.Model): result = self._execute_skill_tool(fname, fargs) if result is None: result = self._execute_tool(fname, fargs) + # 收集合法报表 URL(generate_report / get_report_link 返回的) + if isinstance(result, dict) and result.get('success') and result.get('url'): + legit_report_urls.append(result['url']) messages.append({ 'role': 'tool', 'tool_call_id': tc.get('id'), @@ -869,7 +882,7 @@ class AiConversation(models.Model): final_content = final_content or '错误:工具调用次数超限,请重新提问。' # 检测 LLM 是否编造了报表链接(未通过 generate_report 工具生成) - final_content = self._check_hallucinated_url(final_content) + final_content = self._check_hallucinated_url(final_content, legit_report_urls) # 4. 保存最终 AI 回复 self.env['ai.message'].create({