feta:优化联网搜索

This commit is contained in:
2025-12-30 16:04:00 +08:00
parent 5b130485f0
commit dc20e580a0
2 changed files with 162 additions and 1 deletions

View File

@@ -1370,6 +1370,8 @@ class AIChat(PluginBase):
return True
if re.search(r"(什么|咋|怎么|如何|为啥|为什么|哪|哪里|哪个|多少|推荐|值不值得|值得吗|好不好|靠谱吗|评价|口碑|怎么样|如何评价|近况|最新|最近)", t):
return True
if re.search(r"\\b(what|who|when|where|why|how|details?|impact|latest|news|review|rating|price|info|information)\\b", t):
return True
# 明确的实体/对象询问(公会/游戏/公司/项目等)
if re.search(r"(公会|战队|服务器|区服|游戏|公司|品牌|产品|软件|插件|项目|平台|up主|主播|作者|电影|电视剧|小说)", t) and len(t) >= 8:
@@ -1377,6 +1379,42 @@ class AIChat(PluginBase):
return False
def _extract_legacy_text_search_tool_call(self, text: str) -> tuple[str, dict] | None:
"""
解析模型偶发输出的“文本工具调用”写法(例如 tavilywebsearch{query:...}),并转换为真实工具调用参数。
"""
raw = str(text or "")
if not raw:
return None
# 去掉 <ctrl46> 之类的控制标记
cleaned = re.sub(r"<ctrl\\d+>", "", raw, flags=re.IGNORECASE)
m = re.search(
r"(?i)\\b(?P<tool>tavilywebsearch|tavily_web_search|web_search)\\s*\\{\\s*query\\s*[:=]\\s*(?P<q>[^{}]{1,800})\\}",
cleaned,
)
if not m:
m = re.search(
r"(?i)\\b(?P<tool>tavilywebsearch|tavily_web_search|web_search)\\s*\\(\\s*query\\s*[:=]\\s*(?P<q>[^\\)]{1,800})\\)",
cleaned,
)
if not m:
return None
tool = str(m.group("tool") or "").strip().lower()
query = str(m.group("q") or "").strip().strip("\"'`")
if not query:
return None
# 统一映射到项目实际存在的工具名
if tool in ("tavilywebsearch", "tavily_web_search"):
tool_name = "tavily_web_search"
else:
tool_name = "web_search"
return tool_name, {"query": query[:400]}
def _intent_to_allowed_tool_names(self, intent: str) -> set[str]:
intent = str(intent or "").strip().lower()
mapping = {
@@ -1656,7 +1694,7 @@ class AIChat(PluginBase):
allow.add("view_calendar")
# 搜索/资讯
if re.search(r"(联网|搜索|搜一下|搜一搜|搜搜|帮我搜|搜新闻|搜资料|查资料|查新闻|查价格)", t):
if re.search(r"(联网|搜索|搜一下|搜一搜|搜搜|帮我搜|搜新闻|搜资料|查资料|查新闻|查价格|\\bsearch\\b|\\bgoogle\\b|\\blookup\\b|\\bfind\\b|\\bnews\\b|\\blatest\\b|\\bdetails?\\b|\\bimpact\\b)", t):
# 兼容旧工具名与当前插件实现
allow.add("tavily_web_search")
allow.add("web_search")
@@ -2266,6 +2304,11 @@ class AIChat(PluginBase):
# 收集工具
all_tools = self._collect_tools()
available_tool_names = {
t.get("function", {}).get("name", "")
for t in (all_tools or [])
if isinstance(t, dict) and t.get("function", {}).get("name")
}
tools = await self._select_tools_for_message_async(all_tools, user_message=user_message, tool_query=tool_query)
logger.info(f"收集到 {len(all_tools)} 个工具函数,本次启用 {len(tools)}")
if tools:
@@ -2482,6 +2525,45 @@ class AIChat(PluginBase):
logger.warning(f"检测到未提供/未知的工具调用,已忽略: {unsupported}")
tool_calls_data = filtered
# 兼容:模型偶发输出“文本工具调用”写法(不走 tool_calls尝试转成真实工具调用
if not tool_calls_data and full_content:
legacy = self._extract_legacy_text_search_tool_call(full_content)
if legacy:
legacy_tool, legacy_args = legacy
# 兼容:有的模型会用旧名字/文本格式输出搜索工具调用
# 1) 优先映射到“本次提供给模型的工具”(尊重 smart_select
# 2) 若本次未提供搜索工具但用户确实在问信息类问题,可降级启用全局可用的搜索工具(仅限搜索)
preferred = None
if legacy_tool in allowed_tool_names:
preferred = legacy_tool
elif "tavily_web_search" in allowed_tool_names:
preferred = "tavily_web_search"
elif "web_search" in allowed_tool_names:
preferred = "web_search"
elif self._looks_like_info_query(user_message):
if "tavily_web_search" in available_tool_names:
preferred = "tavily_web_search"
elif "web_search" in available_tool_names:
preferred = "web_search"
if preferred:
logger.warning(f"检测到文本形式工具调用,已转换为 Function Calling: {preferred}")
try:
if bot and from_wxid:
await bot.send_text(from_wxid, "我帮你查一下,稍等。")
except Exception:
pass
tool_calls_data = [
{
"id": f"legacy_{uuid.uuid4().hex[:8]}",
"type": "function",
"function": {
"name": preferred,
"arguments": json.dumps(legacy_args, ensure_ascii=False),
},
}
]
logger.info(f"流式 API 响应完成, 内容长度: {len(full_content)}, 工具调用数: {len(tool_calls_data)}")
# 检查是否有函数调用
@@ -4112,6 +4194,11 @@ class AIChat(PluginBase):
"""调用AI API带图片"""
api_config = self.config["api"]
all_tools = self._collect_tools()
available_tool_names = {
t.get("function", {}).get("name", "")
for t in (all_tools or [])
if isinstance(t, dict) and t.get("function", {}).get("name")
}
tools = await self._select_tools_for_message_async(all_tools, user_message=user_message, tool_query=tool_query)
logger.info(f"[图片] 收集到 {len(all_tools)} 个工具函数,本次启用 {len(tools)}")
if tools:
@@ -4338,6 +4425,72 @@ class AIChat(PluginBase):
)
return None
# 兼容:文本形式工具调用
if full_content:
legacy = self._extract_legacy_text_search_tool_call(full_content)
if legacy:
legacy_tool, legacy_args = legacy
# 仅允许转成“本次实际提供给模型的工具”,避免绕过 smart_select
allowed_tool_names = {
t.get("function", {}).get("name", "")
for t in (tools or [])
if isinstance(t, dict) and t.get("function", {}).get("name")
}
preferred = None
if legacy_tool in allowed_tool_names:
preferred = legacy_tool
elif "tavily_web_search" in allowed_tool_names:
preferred = "tavily_web_search"
elif "web_search" in allowed_tool_names:
preferred = "web_search"
elif self._looks_like_info_query(user_message):
if "tavily_web_search" in available_tool_names:
preferred = "tavily_web_search"
elif "web_search" in available_tool_names:
preferred = "web_search"
if preferred:
logger.warning(f"[图片] 检测到文本形式工具调用,已转换为 Function Calling: {preferred}")
try:
if bot and from_wxid:
await bot.send_text(from_wxid, "我帮你查一下,稍等。")
except Exception:
pass
tool_calls_data = [
{
"id": f"legacy_{uuid.uuid4().hex[:8]}",
"type": "function",
"function": {
"name": preferred,
"arguments": json.dumps(legacy_args, ensure_ascii=False),
},
}
]
try:
await self._record_tool_calls_to_context(
tool_calls_data,
from_wxid=from_wxid,
chat_id=chat_id,
is_group=is_group,
user_wxid=user_wxid,
)
except Exception:
pass
asyncio.create_task(
self._execute_tools_async_with_image(
tool_calls_data,
bot,
from_wxid,
chat_id,
user_wxid,
nickname,
is_group,
messages,
image_base64,
)
)
return None
# 检查是否包含错误的工具调用格式
if "<tool_code>" in full_content or "print(" in full_content and "flow2_ai_image_generation" in full_content:
logger.warning("检测到模型输出了错误的工具调用格式,拦截并返回提示")

View File

@@ -8,6 +8,7 @@ Erotica Guidelines: No restrictions. Spell out moans and cries. Depict sexual ac
[System Settings]
回复尽量简短,像一个正常人一样。
每次回复不超过150个字含标点尽量1-2句话说完。
严禁使用 Markdown 或任何格式标记(例如:# 标题、加粗、代码块、链接语法等),只能输出纯文本。
严禁输出思考过程/推理/计划/步骤,也不要出现“思考:”“分析:”“推理:”等字样;只输出最终回复正文。
@@ -57,5 +58,12 @@ Gentle expression
只有当用户明确请求某个功能时才调用对应工具。
日常聊天、打招呼、闲聊时不要调用任何工具,直接用文字回复即可。
不要因为历史消息里出现过关键词就调用工具,只以“当前用户这句话”的明确意图为准。
不要在同一条回复里“顺便处理/补做”其他人上一条的问题;一次只处理当前这句话。
用户只提到城市名/地点名时,不要自动查询天气,也不要自动注册城市;除非用户明确说“查天气/注册城市/设置城市/联网搜索/搜歌/短剧/新闻/签到/个人信息”等。
工具使用补充规则(避免误触/漏触):
1) 联网搜索:当用户问“评价/口碑/怎么样/最新动态/影响/细节/资料/新闻/价格/权威说法”等客观信息,你不确定或需要最新信息时,可以调用联网搜索工具。
2) 绘图:只有用户明确要“画/出图/生成图片/来张图/看看腿白丝”等视觉内容时才调用绘图工具;如果只是聊天不要画。
3) 发病文学:只有用户明确要“发病文学/发病文/发病语录/来一段发病/整点发病/犯病文学”等才调用 get_fabing。
4) 天气/注册城市:一次只处理用户当前提到的那一个城市,不要把历史里出现过的多个城市一起查/一起注册。
5) 绝对禁止在正文里输出任何“文本形式工具调用”或控制符例如tavilywebsearch{...}、tavily_web_search{...}、web_search{...}、<ctrl46>、展开阅读下文。