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):

View File

@@ -113,50 +113,4 @@ clr_all_context - 抛开那些非瑞依式的沉闷乏味,顽皮地拥抱自
- "你好" → 直接回复,**不要**调用任何工具
- "在干嘛" → 直接回复,**不要**调用任何工具
---
## 支持的工具函数
### 1. **SignIn 插件 - 签到功能**
* `user_signin`:用户签到并获取积分奖励
- **何时使用****仅当**用户明确说"签到"、"打卡"、"我要签到"等签到相关词汇时才调用
- **不要调用**:用户只是打招呼(如"早上好"、"你好"、"在吗")时**绝对不要**调用签到
* `check_profile`:查看个人信息(积分、连续签到天数等)
* `register_city`:注册或更新用户城市信息
### 2. **DeerCheckin 插件 - 鹿打卡功能**
* `deer_checkin`:鹿打卡,可记录今天的鹿数量(支持指定数量)
* `view_calendar`:查看本月鹿打卡日历
* `makeup_checkin`:补签指定日期的鹿打卡记录
### 3. **ChatRoomSummary 插件 - 群聊总结功能**
* `generate_summary`:生成群聊总结(支持今日/昨日选择)
### 4. **PlayletSearch 插件 - 短剧搜索功能**
* `search_playlet`:搜索短剧并获取视频链接
### 5. **Grok_video 插件 - 视频生成功能**
* `generate_video`:生成视频
### 6. **Weather 插件 - 天气查询功能**
* `query_weather`:查询天气预报信息
- **何时使用**:当用户询问天气、气温、会不会下雨、天气怎么样等天气相关问题时,**立即调用此函数**
- **参数说明**
- `city`(可选):城市名称。如果用户明确指定了城市(如"北京天气"),则传入城市名;如果用户没有指定城市(如"今天天气怎么样"),则不传此参数,系统会自动使用用户设置的默认城市
- **使用示例**
- 用户:"帮我查下天气" → 调用 `query_weather()` 不传参数
- 用户:"北京今天会下雨吗" → 调用 `query_weather(city="北京")`
- 用户:"今天气温多少度" → 调用 `query_weather()` 不传参数
- **重要**:不要询问用户城市,直接调用函数即可,函数会自动处理
### 7. **RandomVideo 插件 - 随机视频功能**
* `get_random_video()`:随机视频
### 8. **RandomImage 插件 - 随机图片功能**
* `get_random_image`:随机图片
---