feat:联网搜索和自动回复

This commit is contained in:
2025-12-10 18:50:51 +08:00
parent e0a38eb6f2
commit debb67d71c
6 changed files with 627 additions and 786 deletions

View File

@@ -1606,6 +1606,7 @@ class AIChat(PluginBase):
异步执行工具调用(不阻塞主流程)
AI 已经先回复用户,这里异步执行工具,完成后发送结果
支持 need_ai_reply 标记:工具结果回传给 AI 继续对话(保留上下文和人设)
"""
import json
@@ -1644,10 +1645,14 @@ class AIChat(PluginBase):
if tasks:
results = await asyncio.gather(*tasks, return_exceptions=True)
# 收集需要 AI 回复的工具结果
need_ai_reply_results = []
# 处理每个工具的结果
for i, result in enumerate(results):
tool_info = tool_info_list[i]
function_name = tool_info["function_name"]
tool_call_id = tool_info["tool_call_id"]
if isinstance(result, Exception):
logger.error(f"[异步] 工具 {function_name} 执行异常: {result}")
@@ -1658,6 +1663,15 @@ class AIChat(PluginBase):
if result and result.get("success"):
logger.success(f"[异步] 工具 {function_name} 执行成功")
# 检查是否需要 AI 基于工具结果继续回复
if result.get("need_ai_reply"):
need_ai_reply_results.append({
"tool_call_id": tool_call_id,
"function_name": function_name,
"result": result.get("message", "")
})
continue # 不直接发送,等待 AI 处理
# 如果工具没有自己发送内容,且有消息需要发送
if not result.get("already_sent") and result.get("message"):
# 某些工具可能需要发送结果消息
@@ -1675,6 +1689,13 @@ class AIChat(PluginBase):
if result and result.get("message"):
await bot.send_text(from_wxid, f"{result.get('message')}")
# 如果有需要 AI 回复的工具结果,调用 AI 继续对话
if need_ai_reply_results:
await self._continue_with_tool_results(
need_ai_reply_results, bot, from_wxid, chat_id,
nickname, is_group, messages, tool_calls_data
)
logger.info(f"[异步] 所有工具执行完成")
except Exception as e:
@@ -1686,6 +1707,131 @@ class AIChat(PluginBase):
except:
pass
async def _continue_with_tool_results(self, tool_results: list, bot, from_wxid: str,
chat_id: str, nickname: str, is_group: bool,
messages: list, tool_calls_data: list):
"""
基于工具结果继续调用 AI 对话(保留上下文和人设)
用于 need_ai_reply=True 的工具,如联网搜索等
"""
import json
try:
logger.info(f"[工具回传] 开始基于 {len(tool_results)} 个工具结果继续对话")
# 构建包含工具调用和结果的消息
# 1. 添加 assistant 的工具调用消息
tool_calls_msg = []
for tool_call in tool_calls_data:
tool_call_id = tool_call.get("id", "")
function_name = tool_call.get("function", {}).get("name", "")
arguments_str = tool_call.get("function", {}).get("arguments", "{}")
# 只添加需要 AI 回复的工具
for tr in tool_results:
if tr["tool_call_id"] == tool_call_id:
tool_calls_msg.append({
"id": tool_call_id,
"type": "function",
"function": {
"name": function_name,
"arguments": arguments_str
}
})
break
if tool_calls_msg:
messages.append({
"role": "assistant",
"content": None,
"tool_calls": tool_calls_msg
})
# 2. 添加工具结果消息
for tr in tool_results:
messages.append({
"role": "tool",
"tool_call_id": tr["tool_call_id"],
"content": tr["result"]
})
# 3. 调用 AI 继续对话(不带 tools 参数,避免再次调用工具)
api_config = self.config["api"]
proxy_config = self.config.get("proxy", {})
payload = {
"model": api_config["model"],
"messages": messages,
"max_tokens": api_config.get("max_tokens", 4096),
"stream": True
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_config['api_key']}"
}
proxy = None
if proxy_config.get("enabled", False):
proxy_type = proxy_config.get("type", "http")
proxy_host = proxy_config.get("host", "127.0.0.1")
proxy_port = proxy_config.get("port", 7890)
proxy = f"{proxy_type}://{proxy_host}:{proxy_port}"
timeout = aiohttp.ClientTimeout(total=api_config.get("timeout", 120))
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post(
api_config["url"],
json=payload,
headers=headers,
proxy=proxy
) as resp:
if resp.status != 200:
error_text = await resp.text()
logger.error(f"[工具回传] AI API 错误: {resp.status}, {error_text}")
await bot.send_text(from_wxid, "❌ AI 处理搜索结果失败")
return
# 流式读取响应
full_content = ""
async for line in resp.content:
line = line.decode("utf-8").strip()
if not line or not line.startswith("data: "):
continue
if line == "data: [DONE]":
break
try:
data = json.loads(line[6:])
delta = data.get("choices", [{}])[0].get("delta", {})
content = delta.get("content", "")
if content:
full_content += content
except:
continue
# 发送 AI 的回复
if full_content.strip():
await bot.send_text(from_wxid, full_content.strip())
logger.success(f"[工具回传] AI 回复完成,长度: {len(full_content)}")
# 保存到历史记录
if chat_id:
self._add_to_memory(chat_id, "assistant", full_content.strip())
else:
logger.warning("[工具回传] AI 返回空内容")
except Exception as e:
logger.error(f"[工具回传] 继续对话失败: {e}")
import traceback
logger.error(f"详细错误: {traceback.format_exc()}")
try:
await bot.send_text(from_wxid, "❌ 处理搜索结果时出错")
except:
pass
async def _execute_tools_async_with_image(self, tool_calls_data: list, bot, from_wxid: str,
chat_id: str, nickname: str, is_group: bool,
messages: list, image_base64: str):