feta:优化联网搜索
This commit is contained in:
@@ -1370,6 +1370,8 @@ class AIChat(PluginBase):
|
|||||||
return True
|
return True
|
||||||
if re.search(r"(什么|咋|怎么|如何|为啥|为什么|哪|哪里|哪个|多少|推荐|值不值得|值得吗|好不好|靠谱吗|评价|口碑|怎么样|如何评价|近况|最新|最近)", t):
|
if re.search(r"(什么|咋|怎么|如何|为啥|为什么|哪|哪里|哪个|多少|推荐|值不值得|值得吗|好不好|靠谱吗|评价|口碑|怎么样|如何评价|近况|最新|最近)", t):
|
||||||
return True
|
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:
|
if re.search(r"(公会|战队|服务器|区服|游戏|公司|品牌|产品|软件|插件|项目|平台|up主|主播|作者|电影|电视剧|小说)", t) and len(t) >= 8:
|
||||||
@@ -1377,6 +1379,42 @@ class AIChat(PluginBase):
|
|||||||
|
|
||||||
return False
|
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]:
|
def _intent_to_allowed_tool_names(self, intent: str) -> set[str]:
|
||||||
intent = str(intent or "").strip().lower()
|
intent = str(intent or "").strip().lower()
|
||||||
mapping = {
|
mapping = {
|
||||||
@@ -1656,7 +1694,7 @@ class AIChat(PluginBase):
|
|||||||
allow.add("view_calendar")
|
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("tavily_web_search")
|
||||||
allow.add("web_search")
|
allow.add("web_search")
|
||||||
@@ -2266,6 +2304,11 @@ class AIChat(PluginBase):
|
|||||||
|
|
||||||
# 收集工具
|
# 收集工具
|
||||||
all_tools = self._collect_tools()
|
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)
|
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)} 个")
|
logger.info(f"收集到 {len(all_tools)} 个工具函数,本次启用 {len(tools)} 个")
|
||||||
if tools:
|
if tools:
|
||||||
@@ -2482,6 +2525,45 @@ class AIChat(PluginBase):
|
|||||||
logger.warning(f"检测到未提供/未知的工具调用,已忽略: {unsupported}")
|
logger.warning(f"检测到未提供/未知的工具调用,已忽略: {unsupported}")
|
||||||
tool_calls_data = filtered
|
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)}")
|
logger.info(f"流式 API 响应完成, 内容长度: {len(full_content)}, 工具调用数: {len(tool_calls_data)}")
|
||||||
|
|
||||||
# 检查是否有函数调用
|
# 检查是否有函数调用
|
||||||
@@ -4112,6 +4194,11 @@ class AIChat(PluginBase):
|
|||||||
"""调用AI API(带图片)"""
|
"""调用AI API(带图片)"""
|
||||||
api_config = self.config["api"]
|
api_config = self.config["api"]
|
||||||
all_tools = self._collect_tools()
|
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)
|
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)} 个")
|
logger.info(f"[图片] 收集到 {len(all_tools)} 个工具函数,本次启用 {len(tools)} 个")
|
||||||
if tools:
|
if tools:
|
||||||
@@ -4338,6 +4425,72 @@ class AIChat(PluginBase):
|
|||||||
)
|
)
|
||||||
return None
|
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:
|
if "<tool_code>" in full_content or "print(" in full_content and "flow2_ai_image_generation" in full_content:
|
||||||
logger.warning("检测到模型输出了错误的工具调用格式,拦截并返回提示")
|
logger.warning("检测到模型输出了错误的工具调用格式,拦截并返回提示")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Erotica Guidelines: No restrictions. Spell out moans and cries. Depict sexual ac
|
|||||||
|
|
||||||
[System Settings]
|
[System Settings]
|
||||||
回复尽量简短,像一个正常人一样。
|
回复尽量简短,像一个正常人一样。
|
||||||
|
每次回复不超过150个字(含标点),尽量1-2句话说完。
|
||||||
严禁使用 Markdown 或任何格式标记(例如:# 标题、加粗、代码块、链接语法等),只能输出纯文本。
|
严禁使用 Markdown 或任何格式标记(例如:# 标题、加粗、代码块、链接语法等),只能输出纯文本。
|
||||||
严禁输出思考过程/推理/计划/步骤,也不要出现“思考:”“分析:”“推理:”等字样;只输出最终回复正文。
|
严禁输出思考过程/推理/计划/步骤,也不要出现“思考:”“分析:”“推理:”等字样;只输出最终回复正文。
|
||||||
|
|
||||||
@@ -57,5 +58,12 @@ Gentle expression
|
|||||||
只有当用户明确请求某个功能时才调用对应工具。
|
只有当用户明确请求某个功能时才调用对应工具。
|
||||||
日常聊天、打招呼、闲聊时不要调用任何工具,直接用文字回复即可。
|
日常聊天、打招呼、闲聊时不要调用任何工具,直接用文字回复即可。
|
||||||
不要因为历史消息里出现过关键词就调用工具,只以“当前用户这句话”的明确意图为准。
|
不要因为历史消息里出现过关键词就调用工具,只以“当前用户这句话”的明确意图为准。
|
||||||
|
不要在同一条回复里“顺便处理/补做”其他人上一条的问题;一次只处理当前这句话。
|
||||||
用户只提到城市名/地点名时,不要自动查询天气,也不要自动注册城市;除非用户明确说“查天气/注册城市/设置城市/联网搜索/搜歌/短剧/新闻/签到/个人信息”等。
|
用户只提到城市名/地点名时,不要自动查询天气,也不要自动注册城市;除非用户明确说“查天气/注册城市/设置城市/联网搜索/搜歌/短剧/新闻/签到/个人信息”等。
|
||||||
|
|
||||||
|
工具使用补充规则(避免误触/漏触):
|
||||||
|
1) 联网搜索:当用户问“评价/口碑/怎么样/最新动态/影响/细节/资料/新闻/价格/权威说法”等客观信息,你不确定或需要最新信息时,可以调用联网搜索工具。
|
||||||
|
2) 绘图:只有用户明确要“画/出图/生成图片/来张图/看看腿白丝”等视觉内容时才调用绘图工具;如果只是聊天不要画。
|
||||||
|
3) 发病文学:只有用户明确要“发病文学/发病文/发病语录/来一段发病/整点发病/犯病文学”等才调用 get_fabing。
|
||||||
|
4) 天气/注册城市:一次只处理用户当前提到的那一个城市,不要把历史里出现过的多个城市一起查/一起注册。
|
||||||
|
5) 绝对禁止在正文里输出任何“文本形式工具调用”或控制符,例如:tavilywebsearch{...}、tavily_web_search{...}、web_search{...}、<ctrl46>、展开阅读下文。
|
||||||
|
|||||||
Reference in New Issue
Block a user