{{ getFunctionDisplayChar(func.name) }}
@@ -457,6 +463,13 @@
:checked-replacement-word-ids="checkedReplacementWordIds"
@save="handleTtsSettingsSave"
/>
+
@@ -470,6 +483,7 @@ import RequestService from "@/apis/httpRequest";
import FunctionDialog from "@/components/FunctionDialog.vue";
import ContextProviderDialog from "@/components/ContextProviderDialog.vue";
import TtsAdvancedSettings from "@/components/TtsAdvancedSettings.vue";
+import AgentSnapshotDialog from "@/components/AgentSnapshotDialog.vue";
import HeaderBar from "@/components/HeaderBar.vue";
import i18n from "@/i18n";
import featureManager from "@/utils/featureManager";
@@ -477,11 +491,12 @@ import VersionFooter from "@/components/VersionFooter.vue";
export default {
name: "RoleConfigPage",
- components: { HeaderBar, FunctionDialog, ContextProviderDialog, TtsAdvancedSettings, VersionFooter },
+ components: { HeaderBar, FunctionDialog, ContextProviderDialog, TtsAdvancedSettings, AgentSnapshotDialog, VersionFooter },
data() {
return {
showContextProviderDialog: false,
showTtsAdvancedDialog: false,
+ showSnapshotDialog: false,
ttsSettings: {
volume: 0,
speed: 0,
@@ -529,6 +544,7 @@ export default {
voiceOptions: [],
voiceDetails: {}, // 保存完整的音色信息
showFunctionDialog: false,
+ currentVersionNo: null,
currentFunctions: [],
currentContextProviders: [],
allFunctions: [],
@@ -546,6 +562,7 @@ export default {
asr: false, // 语音识别功能状态
},
dynamicTags: [],
+ originalTagNames: [],
inputVisible: false,
inputValue: '',
checkedReplacementWordIds: []
@@ -555,14 +572,26 @@ export default {
goToHome() {
this.$router.push("/home");
},
- async saveConfig() {
- try {
- await this.handleSaveAgentTags(this.$route.query.agentId);
- } catch (error) {
- console.error('保存标签失败:', error);
- return;
+ normalizeFunctionParams(params, fallback = {}) {
+ if (params === null || params === undefined || params === '') {
+ return { ...fallback };
}
-
+ if (typeof params === 'string') {
+ try {
+ const parsed = JSON.parse(params);
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
+ ? parsed
+ : { ...fallback };
+ } catch (error) {
+ return { ...fallback };
+ }
+ }
+ if (typeof params === 'object' && !Array.isArray(params)) {
+ return { ...params };
+ }
+ return { ...fallback };
+ },
+ async saveConfig() {
const configData = {
agentCode: this.form.agentCode,
agentName: this.form.agentName,
@@ -585,12 +614,17 @@ export default {
functions: this.currentFunctions.map((item) => {
return {
pluginId: item.id,
- paramInfo: item.params,
+ paramInfo: this.normalizeFunctionParams(item.params),
};
}),
contextProviders: this.currentContextProviders,
correctWordFileIds: this.checkedReplacementWordIds,
};
+ const tagNames = this.dynamicTags.map(tag => tag.tagName);
+ const tagsChanged = !this.isSameStringList(tagNames, this.originalTagNames);
+ if (tagsChanged) {
+ configData.tagNames = tagNames;
+ }
// 只在用户设置了TTS参数时才传递(不为null/undefined)
if (this.form.ttsVolume !== null && this.form.ttsVolume !== undefined) {
@@ -602,12 +636,20 @@ export default {
if (this.form.ttsPitch !== null && this.form.ttsPitch !== undefined) {
configData.ttsPitch = this.form.ttsPitch;
}
- Api.agent.updateAgentConfig(this.$route.query.agentId, configData, ({ data }) => {
+ const agentId = this.$route.query.agentId;
+ Api.agent.updateAgentConfig(agentId, configData, ({ data }) => {
if (data.code === 0) {
- this.$message.success({
- message: i18n.t("roleConfig.saveSuccess"),
- showClose: true,
- });
+ const afterSave = () => {
+ if (tagsChanged) {
+ this.originalTagNames = [...tagNames];
+ }
+ this.$message.success({
+ message: i18n.t("roleConfig.saveSuccess"),
+ showClose: true,
+ });
+ this.fetchCurrentVersion(agentId);
+ };
+ afterSave();
} else {
this.$message.error({
message: data.msg || i18n.t("roleConfig.saveFailed"),
@@ -617,6 +659,26 @@ export default {
});
},
+ handleSnapshotRestored() {
+ const agentId = this.$route.query.agentId;
+ if (agentId) {
+ this.fetchAgentConfig(agentId);
+ this.getAgentTags(agentId);
+ this.fetchCurrentVersion(agentId);
+ }
+ },
+ fetchCurrentVersion(agentId) {
+ if (!agentId) {
+ this.currentVersionNo = null;
+ return;
+ }
+
+ Api.agent.getDeviceConfig(agentId, ({ data }) => {
+ if (data.code === 0) {
+ this.currentVersionNo = data.data?.currentVersionNo || null;
+ }
+ });
+ },
resetConfig() {
this.$confirm(i18n.t("roleConfig.confirmReset"), i18n.t("message.info"), {
confirmButtonText: i18n.t("button.ok"),
@@ -754,7 +816,7 @@ export default {
id: mapping.pluginId,
name: meta.name,
// 后端如果还有 paramInfo 字段就用 mapping.paramInfo,否则用 meta.params 默认值
- params: mapping.paramInfo || { ...meta.params },
+ params: this.normalizeFunctionParams(mapping.paramInfo, meta.params),
fieldsMeta: meta.fieldsMeta, // 保留以便对话框渲染 tooltip
};
});
@@ -806,7 +868,7 @@ export default {
});
this.$set(this.modelOptions, model.type, LLMdata);
} else {
- this.$message.error(data.msg || "获取LLM模型列表失败");
+ this.$message.error(data.msg || i18n.t("roleConfig.fetchModelsFailed"));
}
});
}
@@ -1329,7 +1391,7 @@ export default {
handleInputConfirm() {
let inputValue = this.inputValue;
if (inputValue) {
- const tag = { id: new Date().getTime(), tagName: inputValue };
+ const tag = { id: `tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, tagName: inputValue };
this.dynamicTags.push(tag);
}
this.inputVisible = false;
@@ -1339,14 +1401,21 @@ export default {
Api.agent.getAgentTags(agentId, ({ data }) => {
if (data.code === 0) {
this.dynamicTags = data.data || [];
+ this.originalTagNames = this.dynamicTags.map(tag => tag.tagName);
}
});
},
- handleSaveAgentTags(agentId) {
+ isSameStringList(left, right) {
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
+ return false;
+ }
+ return left.every((value, index) => value === right[index]);
+ },
+ handleSaveAgentTags(agentId, tagNames = this.dynamicTags.map(tag => tag.tagName)) {
return new Promise((resolve, reject) => {
- const tagNames = this.dynamicTags.map(tag => tag.tagName);
Api.agent.saveAgentTags(agentId, { tagNames }, ({ data }) => {
if (data.code === 0) {
+ this.originalTagNames = [...tagNames];
resolve();
} else {
reject(data.msg);
@@ -1382,6 +1451,7 @@ export default {
this.fetchAgentConfig(agentId);
this.getAgentTags(agentId);
this.fetchAllFunctions();
+ this.fetchCurrentVersion(agentId);
}
this.fetchModelOptions();
this.fetchTemplates();
@@ -1507,6 +1577,18 @@ export default {
flex-shrink: 0;
}
+.current-version-tag {
+ flex-shrink: 0;
+ padding: 3px 9px;
+ border: 1px solid #dfe7ff;
+ border-radius: 999px;
+ background: #f4f7ff;
+ color: #5778ff;
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 1.5;
+}
+
.more-tag {
cursor: pointer;
flex-shrink: 0;
@@ -1785,6 +1867,16 @@ export default {
font-size: 14px;
}
+.header-actions .history-btn {
+ background: #ffffff;
+ color: #4d5b7c;
+ border: 1px solid #d8dce8;
+ border-radius: 18px;
+ padding: 8px 16px;
+ height: 32px;
+ font-size: 14px;
+}
+
.header-actions .reset-btn {
background: #e6ebff;
color: #5778ff;
diff --git a/main/xiaozhi-server/app.py b/main/xiaozhi-server/app.py
index bdefe42d..9fb3a9ea 100644
--- a/main/xiaozhi-server/app.py
+++ b/main/xiaozhi-server/app.py
@@ -45,7 +45,7 @@ async def monitor_stdin():
async def main():
check_ffmpeg_installed()
- config = load_config()
+ config = await load_config()
# auth_key优先级:配置文件server.auth_key > manager-api.secret > 自动生成
# auth_key用于jwt认证,比如视觉分析接口的jwt认证、ota接口的token生成与websocket认证
diff --git a/main/xiaozhi-server/config/assets/wakeup_words/ed76d459636c2481aec828516c1b4f54.wav b/main/xiaozhi-server/config/assets/wakeup_words/ed76d459636c2481aec828516c1b4f54.wav
deleted file mode 100644
index 1e7246be..00000000
Binary files a/main/xiaozhi-server/config/assets/wakeup_words/ed76d459636c2481aec828516c1b4f54.wav and /dev/null differ
diff --git a/main/xiaozhi-server/config/config_loader.py b/main/xiaozhi-server/config/config_loader.py
index 33a57e72..9e7e1c11 100644
--- a/main/xiaozhi-server/config/config_loader.py
+++ b/main/xiaozhi-server/config/config_loader.py
@@ -7,7 +7,6 @@ from config.manage_api_client import (
get_server_config,
get_agent_models,
get_correct_words,
- lookup_address_book,
DeviceNotFoundException,
DeviceBindException,
)
@@ -24,7 +23,7 @@ def read_config(config_path):
return config
-def load_config():
+async def load_config():
"""加载配置文件"""
from core.utils.cache.manager import cache_manager, CacheType
@@ -41,16 +40,7 @@ def load_config():
custom_config = read_config(custom_config_path)
if custom_config.get("manager-api", {}).get("url"):
- import asyncio
- try:
- loop = asyncio.get_running_loop()
- # 如果已经在事件循环中,使用异步版本
- config = asyncio.run_coroutine_threadsafe(
- get_config_from_api_async(custom_config), loop
- ).result()
- except RuntimeError:
- # 如果不在事件循环中(启动时),创建新的事件循环
- config = asyncio.run(get_config_from_api_async(custom_config))
+ config = await get_config_from_api_async(custom_config)
else:
# 合并配置
config = merge_configs(default_config, custom_config)
@@ -139,7 +129,7 @@ def ensure_directories(config):
selected_provider = selected_modules.get(module_type)
if not selected_provider:
continue
- if config.get(module) is None:
+ if config.get(module_type) is None:
continue
if config.get(selected_provider) is None:
continue
diff --git a/main/xiaozhi-server/config/logger.py b/main/xiaozhi-server/config/logger.py
index ee1b4c36..34737247 100644
--- a/main/xiaozhi-server/config/logger.py
+++ b/main/xiaozhi-server/config/logger.py
@@ -1,9 +1,10 @@
import os
import sys
+import asyncio
from loguru import logger
from config.config_loader import load_config
from config.settings import check_config_file
-from datetime import datetime
+from core.utils.cache.manager import cache_manager, CacheType
SERVER_VERSION = "0.9.5"
_logger_initialized = False
@@ -45,10 +46,15 @@ def formatter(record):
return record["message"]
-def setup_logging():
- check_config_file()
+def setup_logging(config=None):
"""从配置文件中读取日志配置,并设置日志输出格式和级别"""
- config = load_config()
+ if config is None:
+ check_config_file()
+ # 先查缓存,避免在 async 上下文中重复 await load_config
+ config = cache_manager.get(CacheType.CONFIG, "main_config")
+ if config is None:
+ # 缓存也没有(理论上不该发生),才走 asyncio.run
+ config = asyncio.run(load_config())
log_config = config["log"]
global _logger_initialized
diff --git a/main/xiaozhi-server/config/manage_api_client.py b/main/xiaozhi-server/config/manage_api_client.py
index 7cc4c361..8eb2da20 100644
--- a/main/xiaozhi-server/config/manage_api_client.py
+++ b/main/xiaozhi-server/config/manage_api_client.py
@@ -73,6 +73,7 @@ class ManageApiClient:
},
timeout=cls.config.get("timeout", 30),
limits=limits, # 使用限制
+ trust_env=False,
)
return cls._async_clients[loop_id]
except RuntimeError:
diff --git a/main/xiaozhi-server/config/settings.py b/main/xiaozhi-server/config/settings.py
index fb8868b4..cfca89b6 100644
--- a/main/xiaozhi-server/config/settings.py
+++ b/main/xiaozhi-server/config/settings.py
@@ -1,4 +1,5 @@
import os
+import asyncio
from config.config_loader import read_config, get_project_dir, load_config
@@ -20,7 +21,7 @@ def check_config_file():
)
# 检查是否从API读取配置
- config = load_config()
+ config = asyncio.run(load_config())
if config.get("read_config_from_api", False):
print("从API读取配置")
old_config_origin = read_config(custom_config_file)
diff --git a/main/xiaozhi-server/core/providers/asr/fun_local.py b/main/xiaozhi-server/core/providers/asr/fun_local.py
index e2b93521..5547104d 100644
--- a/main/xiaozhi-server/core/providers/asr/fun_local.py
+++ b/main/xiaozhi-server/core/providers/asr/fun_local.py
@@ -43,9 +43,13 @@ class ASRProvider(ASRProviderBase):
# 内存检测,要求大于2G
min_mem_bytes = 2 * 1024 * 1024 * 1024
- total_mem = psutil.virtual_memory().total
- if total_mem < min_mem_bytes:
- logger.bind(tag=TAG).error(f"可用内存不足2G,当前仅有 {total_mem / (1024*1024):.2f} MB,可能无法启动FunASR")
+ try:
+ total_mem = psutil.virtual_memory().total
+ except RuntimeError as e:
+ logger.bind(tag=TAG).warning(f"获取系统内存信息失败,跳过FunASR内存检测: {e}")
+ else:
+ if total_mem < min_mem_bytes:
+ logger.bind(tag=TAG).error(f"可用内存不足2G,当前仅有 {total_mem / (1024*1024):.2f} MB,可能无法启动FunASR")
self.interface_type = InterfaceType.LOCAL
self.model_dir = config.get("model_dir")
diff --git a/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py b/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py
index cbf410ae..2dc93358 100644
--- a/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py
+++ b/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py
@@ -1,4 +1,5 @@
import json
+import asyncio
import traceback
from ..base import MemoryProviderBase, logger
@@ -59,7 +60,9 @@ class MemoryProvider(MemoryProviderBase):
messages.append({"role": message.role, "content": content})
- result = self.client.add(messages, user_id=self.role_id)
+ result = await asyncio.to_thread(
+ self.client.add, messages, user_id=self.role_id
+ )
logger.bind(tag=TAG).debug(f"Save memory result: {result}")
except Exception as e:
logger.bind(tag=TAG).error(f"保存记忆失败: {str(e)}")
@@ -84,7 +87,9 @@ class MemoryProvider(MemoryProviderBase):
except (json.JSONDecodeError, KeyError):
pass
- results = self.client.search(search_query, filters=filters)
+ results = await asyncio.to_thread(
+ self.client.search, search_query, filters=filters
+ )
if not results or "results" not in results:
return ""
diff --git a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py
index 3e96fdfd..72d6526f 100644
--- a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py
+++ b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py
@@ -186,13 +186,19 @@ class MemoryProvider(MemoryProviderBase):
messages.append({"role": message.role, "content": content})
# Add memory using PowerMem SDK
- result = self.memory_client.add(
- messages=messages,
- user_id=self.role_id
- )
- # Handle both sync and async returns
- if asyncio.iscoroutine(result):
- result = await result
+ if self.enable_user_profile:
+ # UserMemory uses sync add
+ result = await asyncio.to_thread(
+ self.memory_client.add,
+ messages=messages,
+ user_id=self.role_id
+ )
+ else:
+ # AsyncMemory uses async add
+ result = await self.memory_client.add(
+ messages=messages,
+ user_id=self.role_id
+ )
logger.bind(tag=TAG).debug(f"Save memory result: {result}")
diff --git a/main/xiaozhi-server/core/providers/tools/server_plugins/plugin_executor.py b/main/xiaozhi-server/core/providers/tools/server_plugins/plugin_executor.py
index 249e040c..37f4fb9d 100644
--- a/main/xiaozhi-server/core/providers/tools/server_plugins/plugin_executor.py
+++ b/main/xiaozhi-server/core/providers/tools/server_plugins/plugin_executor.py
@@ -1,5 +1,6 @@
"""服务端插件工具执行器"""
+import asyncio
from typing import Dict, Any, TYPE_CHECKING
if TYPE_CHECKING:
@@ -41,6 +42,10 @@ class ServerPluginExecutor(ToolExecutor):
# 默认不传conn参数
result = func_item.func(**arguments)
+ # 兼容 async def 工具函数
+ if asyncio.iscoroutine(result):
+ result = await result
+
return result
except Exception as e:
diff --git a/main/xiaozhi-server/core/utils/prompt_manager.py b/main/xiaozhi-server/core/utils/prompt_manager.py
index af422529..edae6e9a 100644
--- a/main/xiaozhi-server/core/utils/prompt_manager.py
+++ b/main/xiaozhi-server/core/utils/prompt_manager.py
@@ -4,6 +4,8 @@
"""
import os
+import asyncio
+import threading
from typing import Dict, Any, TYPE_CHECKING
if TYPE_CHECKING:
@@ -169,12 +171,33 @@ class PromptManager:
if cached_weather is not None:
return cached_weather
- # 缓存未命中,调用get_weather函数获取
+ # 缓存未命中,调用 async get_weather 函数
+ # Windows ProactorEventLoop 不支持 run_coroutine_threadsafe().result()
+ # 因此用 call_soon_threadsafe 提交任务 + threading.Event 等待结果
+ # 注意:Event.wait() 只阻塞当前线程池线程,不阻塞主事件循环
from plugins_func.functions.get_weather import get_weather
from plugins_func.register import ActionResponse
- # 调用get_weather函数
- result = get_weather(conn, location=location, lang="zh_CN")
+ result_holder = []
+ exception_holder = []
+
+ async def _call():
+ try:
+ result_holder.append(
+ await get_weather(conn, location=location, lang="zh_CN")
+ )
+ except Exception as e:
+ exception_holder.append(e)
+ finally:
+ event.set()
+
+ event = threading.Event()
+ conn.loop.call_soon_threadsafe(lambda: asyncio.ensure_future(_call()))
+ if not event.wait(timeout=10):
+ raise TimeoutError("获取天气信息超时")
+ if exception_holder:
+ raise exception_holder[0]
+ result = result_holder[0]
if isinstance(result, ActionResponse):
weather_report = result.result
self.cache_manager.set(self.CacheType.WEATHER, location, weather_report)
diff --git a/main/xiaozhi-server/core/websocket_server.py b/main/xiaozhi-server/core/websocket_server.py
index 077f7f4d..2ef7cda7 100644
--- a/main/xiaozhi-server/core/websocket_server.py
+++ b/main/xiaozhi-server/core/websocket_server.py
@@ -42,7 +42,7 @@ TAG = __name__
class WebSocketServer:
def __init__(self, config: dict):
self.config = config
- self.logger = setup_logging()
+ self.logger = setup_logging(config)
self.config_lock = asyncio.Lock()
modules = initialize_modules(
self.logger,
diff --git a/main/xiaozhi-server/plugins_func/functions/call_device.py b/main/xiaozhi-server/plugins_func/functions/call_device.py
index cfca1f33..e2b08fb5 100644
--- a/main/xiaozhi-server/plugins_func/functions/call_device.py
+++ b/main/xiaozhi-server/plugins_func/functions/call_device.py
@@ -1,9 +1,8 @@
"""呼叫设备工具"""
-import requests
-from typing import TYPE_CHECKING
-
+import httpx
from config.logger import setup_logging
from plugins_func.register import register_function, ToolType, ActionResponse, Action
+from typing import TYPE_CHECKING
if TYPE_CHECKING:
from core.connection import ConnectionHandler
@@ -35,8 +34,9 @@ call_device_function_desc = {
}
-def _request_api(url: str, params: dict, headers: dict) -> requests.Response:
- return requests.get(url, params=params, headers=headers, timeout=10)
+async def _request_api(url: str, params: dict, headers: dict):
+ async with httpx.AsyncClient(timeout=httpx.Timeout(10.0, connect=3.0)) as client:
+ return await client.get(url, params=params, headers=headers)
def _failed_reply(msg: str) -> ActionResponse:
@@ -49,7 +49,7 @@ def _is_answering(conn: "ConnectionHandler") -> bool:
@register_function("call_device", call_device_function_desc, ToolType.SYSTEM_CTL)
-def call_device(conn: "ConnectionHandler", nickname: str):
+async def call_device(conn: "ConnectionHandler", nickname: str):
caller_mac = conn.headers.get("device-id")
if not caller_mac:
return _failed_reply("无法获取本机MAC地址")
@@ -71,13 +71,13 @@ def call_device(conn: "ConnectionHandler", nickname: str):
# 查询通讯录并发起呼叫
try:
- resp = _request_api(
+ resp = await _request_api(
f"{api_url}/device/address-book/call",
params=params,
headers=headers,
)
result = resp.json()
- except requests.RequestException as e:
+ except httpx.HTTPError as e:
logger.bind(tag=TAG).error(f"呼叫请求失败: {e}")
return _failed_reply("呼叫失败,请稍后再试")
diff --git a/main/xiaozhi-server/plugins_func/functions/get_news_from_chinanews.py b/main/xiaozhi-server/plugins_func/functions/get_news_from_chinanews.py
index cf64825b..94cec718 100644
--- a/main/xiaozhi-server/plugins_func/functions/get_news_from_chinanews.py
+++ b/main/xiaozhi-server/plugins_func/functions/get_news_from_chinanews.py
@@ -1,5 +1,5 @@
import random
-import requests
+import httpx
import xml.etree.ElementTree as ET
from bs4 import BeautifulSoup
from config.logger import setup_logging
@@ -44,11 +44,11 @@ GET_NEWS_FROM_CHINANEWS_FUNCTION_DESC = {
}
-def fetch_news_from_rss(rss_url):
+async def fetch_news_from_rss(rss_url):
"""从RSS源获取新闻列表"""
try:
- response = requests.get(rss_url)
- response.raise_for_status()
+ async with httpx.AsyncClient(timeout=httpx.Timeout(5.0, connect=3.0)) as client:
+ response = await client.get(rss_url)
# 解析XML
root = ET.fromstring(response.content)
@@ -86,11 +86,11 @@ def fetch_news_from_rss(rss_url):
return []
-def fetch_news_detail(url):
+async def fetch_news_detail(url):
"""获取新闻详情页内容并总结"""
try:
- response = requests.get(url)
- response.raise_for_status()
+ async with httpx.AsyncClient(timeout=httpx.Timeout(10.0, connect=3.0)) as client:
+ response = await client.get(url)
soup = BeautifulSoup(response.content, "html.parser")
@@ -148,7 +148,7 @@ def map_category(category_text):
GET_NEWS_FROM_CHINANEWS_FUNCTION_DESC,
ToolType.SYSTEM_CTL,
)
-def get_news_from_chinanews(
+async def get_news_from_chinanews(
conn: "ConnectionHandler",
category: str = None,
detail: bool = False,
@@ -180,7 +180,7 @@ def get_news_from_chinanews(
logger.bind(tag=TAG).debug(f"获取新闻详情: {title}, URL={link}")
# 获取新闻详情
- detail_content = fetch_news_detail(link)
+ detail_content = await fetch_news_detail(link)
if not detail_content or detail_content == "无法获取详细内容":
return ActionResponse(
@@ -220,7 +220,7 @@ def get_news_from_chinanews(
)
# 获取新闻列表
- news_items = fetch_news_from_rss(rss_url)
+ news_items = await fetch_news_from_rss(rss_url)
if not news_items:
return ActionResponse(
diff --git a/main/xiaozhi-server/plugins_func/functions/get_news_from_newsnow.py b/main/xiaozhi-server/plugins_func/functions/get_news_from_newsnow.py
index cbf48cf0..8f0df016 100644
--- a/main/xiaozhi-server/plugins_func/functions/get_news_from_newsnow.py
+++ b/main/xiaozhi-server/plugins_func/functions/get_news_from_newsnow.py
@@ -1,9 +1,8 @@
import random
-import requests
-import json
+import httpx
+from markitdown import MarkItDown
from config.logger import setup_logging
from plugins_func.register import register_function, ToolType, ActionResponse, Action
-from markitdown import MarkItDown
from typing import TYPE_CHECKING
if TYPE_CHECKING:
@@ -110,7 +109,7 @@ GET_NEWS_FROM_NEWSNOW_FUNCTION_DESC = {
}
-def fetch_news_from_api(conn: "ConnectionHandler", source="thepaper"):
+async def fetch_news_from_api(conn: "ConnectionHandler", source="thepaper"):
"""从API获取新闻列表"""
try:
api_url = f"https://newsnow.busiyi.world/api/s?id={source}"
@@ -120,8 +119,8 @@ def fetch_news_from_api(conn: "ConnectionHandler", source="thepaper"):
api_url = news_config["url"] + source
headers = {"User-Agent": "Mozilla/5.0"}
- response = requests.get(api_url, headers=headers, timeout=10)
- response.raise_for_status()
+ async with httpx.AsyncClient(timeout=httpx.Timeout(10.0, connect=3.0)) as client:
+ response = await client.get(api_url, headers=headers)
data = response.json()
@@ -136,12 +135,12 @@ def fetch_news_from_api(conn: "ConnectionHandler", source="thepaper"):
return []
-def fetch_news_detail(url):
+async def fetch_news_detail(url):
"""获取新闻详情页内容并使用MarkItDown清理HTML"""
try:
headers = {"User-Agent": "Mozilla/5.0"}
- response = requests.get(url, headers=headers, timeout=10)
- response.raise_for_status()
+ async with httpx.AsyncClient(timeout=httpx.Timeout(10.0, connect=3.0)) as client:
+ response = await client.get(url, headers=headers)
# 使用MarkItDown清理HTML内容
md = MarkItDown(enable_plugins=False)
@@ -166,7 +165,7 @@ def fetch_news_detail(url):
GET_NEWS_FROM_NEWSNOW_FUNCTION_DESC,
ToolType.SYSTEM_CTL,
)
-def get_news_from_newsnow(
+async def get_news_from_newsnow(
conn: "ConnectionHandler",
source: str = "澎湃新闻",
detail: bool = False,
@@ -206,7 +205,7 @@ def get_news_from_newsnow(
)
# 获取新闻详情
- detail_content = fetch_news_detail(url)
+ detail_content = await fetch_news_detail(url)
if not detail_content or detail_content == "无法获取详细内容":
return ActionResponse(
@@ -248,7 +247,7 @@ def get_news_from_newsnow(
logger.bind(tag=TAG).info(f"获取新闻: 新闻源={source}({english_source_id})")
# 获取新闻列表
- news_items = fetch_news_from_api(conn, english_source_id)
+ news_items = await fetch_news_from_api(conn, english_source_id)
if not news_items:
return ActionResponse(
diff --git a/main/xiaozhi-server/plugins_func/functions/get_weather.py b/main/xiaozhi-server/plugins_func/functions/get_weather.py
index 88870529..c0c4118e 100644
--- a/main/xiaozhi-server/plugins_func/functions/get_weather.py
+++ b/main/xiaozhi-server/plugins_func/functions/get_weather.py
@@ -1,4 +1,4 @@
-import requests
+import httpx
from bs4 import BeautifulSoup
from config.logger import setup_logging
from plugins_func.register import register_function, ToolType, ActionResponse, Action
@@ -111,20 +111,23 @@ WEATHER_CODE_MAP = {
}
-def fetch_city_info(location, api_key, api_host):
+async def fetch_city_info(location, api_key, api_host):
url = f"https://{api_host}/geo/v2/city/lookup?key={api_key}&location={location}&lang=zh"
- response = requests.get(url, headers=HEADERS).json()
- if response.get("error") is not None:
+ async with httpx.AsyncClient(timeout=httpx.Timeout(5.0, connect=3.0)) as client:
+ response = await client.get(url, headers=HEADERS)
+ data = response.json()
+ if data.get("error") is not None:
logger.bind(tag=TAG).error(
- f"获取天气失败,原因:{response.get('error', {}).get('detail')}"
+ f"获取天气失败,原因:{data.get('error', {}).get('detail')}"
)
return None
- return response.get("location", [])[0] if response.get("location") else None
+ return data.get("location", [])[0] if data.get("location") else None
-def fetch_weather_page(url):
- response = requests.get(url, headers=HEADERS)
- return BeautifulSoup(response.text, "html.parser") if response.ok else None
+async def fetch_weather_page(url):
+ async with httpx.AsyncClient(timeout=httpx.Timeout(10.0, connect=3.0)) as client:
+ response = await client.get(url, headers=HEADERS)
+ return BeautifulSoup(response.text, "html.parser") if response.status_code == 200 else None
def parse_weather_info(soup):
@@ -159,7 +162,7 @@ def parse_weather_info(soup):
@register_function("get_weather", GET_WEATHER_FUNCTION_DESC, ToolType.SYSTEM_CTL)
-def get_weather(conn: "ConnectionHandler", location: str = None, lang: str = "zh_CN"):
+async def get_weather(conn: "ConnectionHandler", location: str = None, lang: str = "zh_CN"):
from core.utils.cache.manager import cache_manager, CacheType
weather_config = conn.config.get("plugins", {}).get("get_weather", {})
@@ -195,12 +198,12 @@ def get_weather(conn: "ConnectionHandler", location: str = None, lang: str = "zh
return ActionResponse(Action.REQLLM, cached_weather_report, None)
# 缓存未命中,获取实时天气数据
- city_info = fetch_city_info(location, api_key, api_host)
+ city_info = await fetch_city_info(location, api_key, api_host)
if not city_info:
return ActionResponse(
Action.REQLLM, f"未找到相关的城市: {location},请确认地点是否正确", None
)
- soup = fetch_weather_page(city_info["fxLink"])
+ soup = await fetch_weather_page(city_info["fxLink"])
if not soup:
return ActionResponse(Action.REQLLM, None, "请求失败")
city_name, current_abstract, current_basic, temps_list = parse_weather_info(soup)
diff --git a/main/xiaozhi-server/plugins_func/functions/hass_get_state.py b/main/xiaozhi-server/plugins_func/functions/hass_get_state.py
index 86e866c1..e56fe5a8 100644
--- a/main/xiaozhi-server/plugins_func/functions/hass_get_state.py
+++ b/main/xiaozhi-server/plugins_func/functions/hass_get_state.py
@@ -1,8 +1,7 @@
-from plugins_func.register import register_function, ToolType, ActionResponse, Action
-from plugins_func.functions.hass_init import initialize_hass_handler
+import httpx
from config.logger import setup_logging
-import asyncio
-import requests
+from plugins_func.functions.hass_init import initialize_hass_handler
+from plugins_func.register import register_function, ToolType, ActionResponse, Action
from typing import TYPE_CHECKING
if TYPE_CHECKING:
@@ -31,11 +30,11 @@ hass_get_state_function_desc = {
@register_function("hass_get_state", hass_get_state_function_desc, ToolType.SYSTEM_CTL)
-def hass_get_state(conn: "ConnectionHandler", entity_id=""):
+async def hass_get_state(conn: "ConnectionHandler", entity_id=""):
try:
- ha_response = handle_hass_get_state(conn, entity_id)
+ ha_response = await handle_hass_get_state(conn, entity_id)
return ActionResponse(Action.REQLLM, ha_response, None)
- except asyncio.TimeoutError:
+ except httpx.TimeoutException:
logger.bind(tag=TAG).error("获取Home Assistant状态超时")
return ActionResponse(Action.ERROR, "请求超时", None)
except Exception as e:
@@ -44,13 +43,16 @@ def hass_get_state(conn: "ConnectionHandler", entity_id=""):
return ActionResponse(Action.ERROR, error_msg, None)
-def handle_hass_get_state(conn: "ConnectionHandler", entity_id):
+async def handle_hass_get_state(conn: "ConnectionHandler", entity_id):
ha_config = initialize_hass_handler(conn)
api_key = ha_config.get("api_key")
base_url = ha_config.get("base_url")
url = f"{base_url}/api/states/{entity_id}"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
- response = requests.get(url, headers=headers, timeout=5)
+
+ async with httpx.AsyncClient(timeout=httpx.Timeout(5.0, connect=3.0)) as client:
+ response = await client.get(url, headers=headers)
+
if response.status_code == 200:
responsetext = "设备状态:" + response.json()["state"] + " "
logger.bind(tag=TAG).info(f"api返回内容: {response.json()}")
@@ -92,8 +94,5 @@ def handle_hass_get_state(conn: "ConnectionHandler", entity_id):
)
logger.bind(tag=TAG).info(f"查询返回内容: {responsetext}")
return responsetext
- # return response.json()['attributes']
- # response.attributes
-
else:
return f"切换失败,错误码: {response.status_code}"
diff --git a/main/xiaozhi-server/plugins_func/functions/hass_play_music.py b/main/xiaozhi-server/plugins_func/functions/hass_play_music.py
index 7bd9f2a1..0d0b3eb7 100644
--- a/main/xiaozhi-server/plugins_func/functions/hass_play_music.py
+++ b/main/xiaozhi-server/plugins_func/functions/hass_play_music.py
@@ -1,8 +1,7 @@
-from plugins_func.register import register_function, ToolType, ActionResponse, Action
-from plugins_func.functions.hass_init import initialize_hass_handler
+import httpx
from config.logger import setup_logging
-import asyncio
-import requests
+from plugins_func.functions.hass_init import initialize_hass_handler
+from plugins_func.register import register_function, ToolType, ActionResponse, Action
from typing import TYPE_CHECKING
if TYPE_CHECKING:
@@ -37,18 +36,17 @@ hass_play_music_function_desc = {
@register_function(
"hass_play_music", hass_play_music_function_desc, ToolType.SYSTEM_CTL
)
-def hass_play_music(conn: "ConnectionHandler", entity_id="", media_content_id="random"):
+async def hass_play_music(conn: "ConnectionHandler", entity_id="", media_content_id="random"):
try:
- # 执行音乐播放命令
- future = asyncio.run_coroutine_threadsafe(
- handle_hass_play_music(conn, entity_id, media_content_id), conn.loop
- )
- ha_response = future.result()
+ result = await handle_hass_play_music(conn, entity_id, media_content_id)
return ActionResponse(
- action=Action.RESPONSE, result="退出意图已处理", response=ha_response
+ action=Action.RECORD, result="指令已接收", response=result
)
except Exception as e:
logger.bind(tag=TAG).error(f"处理音乐意图错误: {e}")
+ return ActionResponse(
+ action=Action.RESPONSE, result=str(e), response="播放音乐时出错了"
+ )
async def handle_hass_play_music(
@@ -60,7 +58,10 @@ async def handle_hass_play_music(
url = f"{base_url}/api/services/music_assistant/play_media"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
data = {"entity_id": entity_id, "media_id": media_content_id}
- response = requests.post(url, headers=headers, json=data)
+
+ async with httpx.AsyncClient(timeout=httpx.Timeout(10.0, connect=3.0)) as client:
+ response = await client.post(url, headers=headers, json=data)
+
if response.status_code == 200:
return f"正在播放{media_content_id}的音乐"
else:
diff --git a/main/xiaozhi-server/plugins_func/functions/hass_set_state.py b/main/xiaozhi-server/plugins_func/functions/hass_set_state.py
index 62807f9b..293ca9e2 100644
--- a/main/xiaozhi-server/plugins_func/functions/hass_set_state.py
+++ b/main/xiaozhi-server/plugins_func/functions/hass_set_state.py
@@ -1,8 +1,7 @@
-from plugins_func.register import register_function, ToolType, ActionResponse, Action
-from plugins_func.functions.hass_init import initialize_hass_handler
+import httpx
from config.logger import setup_logging
-import asyncio
-import requests
+from plugins_func.functions.hass_init import initialize_hass_handler
+from plugins_func.register import register_function, ToolType, ActionResponse, Action
from typing import TYPE_CHECKING
if TYPE_CHECKING:
@@ -54,13 +53,13 @@ hass_set_state_function_desc = {
@register_function("hass_set_state", hass_set_state_function_desc, ToolType.SYSTEM_CTL)
-def hass_set_state(conn: "ConnectionHandler", entity_id="", state=None):
+async def hass_set_state(conn: "ConnectionHandler", entity_id="", state=None):
if state is None:
state = {}
try:
- ha_response = handle_hass_set_state(conn, entity_id, state)
+ ha_response = await handle_hass_set_state(conn, entity_id, state)
return ActionResponse(Action.REQLLM, ha_response, None)
- except asyncio.TimeoutError:
+ except httpx.TimeoutException:
logger.bind(tag=TAG).error("设置Home Assistant状态超时")
return ActionResponse(Action.ERROR, "请求超时", None)
except Exception as e:
@@ -69,7 +68,7 @@ def hass_set_state(conn: "ConnectionHandler", entity_id="", state=None):
return ActionResponse(Action.ERROR, error_msg, None)
-def handle_hass_set_state(conn: "ConnectionHandler", entity_id, state):
+async def handle_hass_set_state(conn: "ConnectionHandler", entity_id, state):
ha_config = initialize_hass_handler(conn)
api_key = ha_config.get("api_key")
base_url = ha_config.get("base_url")
@@ -169,7 +168,10 @@ def handle_hass_set_state(conn: "ConnectionHandler", entity_id, state):
data = {"entity_id": entity_id, arg: value}
url = f"{base_url}/api/services/{domain}/{action}"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
- response = requests.post(url, headers=headers, json=data, timeout=5) # 设置5秒超时
+
+ async with httpx.AsyncClient(timeout=httpx.Timeout(5.0, connect=3.0)) as client:
+ response = await client.post(url, headers=headers, json=data)
+
logger.bind(tag=TAG).info(
f"设置状态:{description},url:{url},return_code:{response.status_code}"
)
diff --git a/main/xiaozhi-server/plugins_func/functions/play_music.py b/main/xiaozhi-server/plugins_func/functions/play_music.py
index 18c605d2..fd21f9f8 100644
--- a/main/xiaozhi-server/plugins_func/functions/play_music.py
+++ b/main/xiaozhi-server/plugins_func/functions/play_music.py
@@ -5,10 +5,8 @@ import random
import difflib
import traceback
from pathlib import Path
-from core.handle.sendAudioHandle import send_stt_message
-from plugins_func.register import register_function, ToolType, ActionResponse, Action
-from core.utils.dialogue import Message
from core.providers.tts.dto.dto import TTSMessageDTO, SentenceType, ContentType
+from plugins_func.register import register_function, ToolType, ActionResponse, Action
from typing import TYPE_CHECKING
if TYPE_CHECKING:
@@ -38,34 +36,12 @@ play_music_function_desc = {
@register_function("play_music", play_music_function_desc, ToolType.SYSTEM_CTL)
-def play_music(conn: "ConnectionHandler", song_name: str):
+async def play_music(conn: "ConnectionHandler", song_name: str):
try:
music_intent = (
f"播放音乐 {song_name}" if song_name != "random" else "随机播放音乐"
)
-
- # 检查事件循环状态
- if not conn.loop.is_running():
- conn.logger.bind(tag=TAG).error("事件循环未运行,无法提交任务")
- return ActionResponse(
- action=Action.RESPONSE, result="系统繁忙", response="请稍后再试"
- )
-
- # 提交异步任务
- task = conn.loop.create_task(
- handle_music_command(conn, music_intent) # 封装异步逻辑
- )
-
- # 非阻塞回调处理
- def handle_done(f):
- try:
- f.result() # 可在此处理成功逻辑
- conn.logger.bind(tag=TAG).info("播放完成")
- except Exception as e:
- conn.logger.bind(tag=TAG).error(f"播放失败: {e}")
-
- task.add_done_callback(handle_done)
-
+ await handle_music_command(conn, music_intent)
return ActionResponse(
action=Action.RECORD, result="指令已接收", response="正在为您播放音乐"
)
diff --git a/main/xiaozhi-server/plugins_func/functions/search_from_ragflow.py b/main/xiaozhi-server/plugins_func/functions/search_from_ragflow.py
index 79d546ee..40abf8c4 100644
--- a/main/xiaozhi-server/plugins_func/functions/search_from_ragflow.py
+++ b/main/xiaozhi-server/plugins_func/functions/search_from_ragflow.py
@@ -1,5 +1,5 @@
-import requests
-import sys
+import json
+import httpx
from config.logger import setup_logging
from plugins_func.register import register_function, ToolType, ActionResponse, Action
from typing import TYPE_CHECKING
@@ -28,7 +28,7 @@ SEARCH_FROM_RAGFLOW_FUNCTION_DESC = {
@register_function(
"search_from_ragflow", SEARCH_FROM_RAGFLOW_FUNCTION_DESC, ToolType.SYSTEM_CTL
)
-def search_from_ragflow(conn: "ConnectionHandler", question=None):
+async def search_from_ragflow(conn: "ConnectionHandler", question=None):
# 确保字符串参数正确处理编码
if question and isinstance(question, str):
# 确保问题参数是UTF-8编码的字符串
@@ -49,13 +49,8 @@ def search_from_ragflow(conn: "ConnectionHandler", question=None):
try:
# 使用ensure_ascii=False确保JSON序列化时正确处理中文
- response = requests.post(
- url,
- json=payload,
- headers=headers,
- timeout=5,
- verify=False,
- )
+ async with httpx.AsyncClient(timeout=httpx.Timeout(5.0, connect=3.0), verify=False) as client:
+ response = await client.post(url, json=payload, headers=headers)
# 显式设置响应的编码为utf-8
response.encoding = "utf-8"
@@ -64,7 +59,6 @@ def search_from_ragflow(conn: "ConnectionHandler", question=None):
# 先获取文本内容,然后手动处理JSON解码
response_text = response.text
- import json
result = json.loads(response_text)
@@ -110,48 +104,30 @@ def search_from_ragflow(conn: "ConnectionHandler", question=None):
context_text = "根据知识库查询结果,没有相关信息。"
return ActionResponse(Action.REQLLM, context_text, None)
- except requests.exceptions.RequestException as e:
- # 网络请求异常
- error_type = type(e).__name__
- logger.bind(tag=TAG).error(
- f"RAGflow网络请求失败,异常类型:{error_type},详情:{str(e)}"
- )
-
- # 根据异常类型提供更详细的错误信息和解决方案
- if isinstance(e, requests.exceptions.ConnectTimeout):
- error_response = "RAG接口连接超时(5秒)"
- error_response += "\n可能原因:RAGflow服务未启动或网络连接问题"
- error_response += "\n解决方案:请检查RAGflow服务状态和网络连接"
-
- elif isinstance(e, requests.exceptions.ConnectionError):
- error_response = "无法连接到RAG接口"
- error_response += "\n可能原因:RAGflow服务地址错误或服务未运行"
- error_response += "\n解决方案:请检查RAGflow服务地址配置和服务状态"
-
- elif isinstance(e, requests.exceptions.Timeout):
- error_response = "RAG接口请求超时"
- error_response += "\n可能原因:RAGflow服务响应缓慢或网络延迟"
- error_response += "\n解决方案:请稍后重试或检查RAGflow服务性能"
-
- elif isinstance(e, requests.exceptions.HTTPError):
- # 处理HTTP错误状态码
- if hasattr(e.response, "status_code"):
- status_code = e.response.status_code
- error_response = f"RAG接口HTTP错误(状态码:{status_code})"
-
- # 尝试获取响应内容中的错误信息
- try:
- error_detail = e.response.json().get("error", {}).get("message", "")
- if error_detail:
- error_response += f"\n错误详情:{error_detail}"
- except:
- pass
- else:
- error_response = f"RAG接口HTTP异常:{str(e)}"
+ except httpx.TimeoutException as e:
+ error_response = "RAG接口请求超时"
+ error_response += "\n可能原因:RAGflow服务响应缓慢或网络延迟"
+ error_response += "\n解决方案:请稍后重试或检查RAGflow服务性能"
+ return ActionResponse(Action.RESPONSE, None, error_response)
+ except httpx.HTTPStatusError as e:
+ if hasattr(e.response, "status_code"):
+ status_code = e.response.status_code
+ error_response = f"RAG接口HTTP错误(状态码:{status_code})"
+ try:
+ error_detail = e.response.json().get("error", {}).get("message", "")
+ if error_detail:
+ error_response += f"\n错误详情:{error_detail}"
+ except:
+ pass
else:
- error_response = f"RAG接口网络异常({error_type}):{str(e)}"
+ error_response = f"RAG接口HTTP异常:{str(e)}"
+ return ActionResponse(Action.RESPONSE, None, error_response)
+ except httpx.HTTPError as e:
+ error_response = "无法连接到RAG接口"
+ error_response += "\n可能原因:RAGflow服务地址错误或服务未运行"
+ error_response += "\n解决方案:请检查RAGflow服务地址配置和服务状态"
return ActionResponse(Action.RESPONSE, None, error_response)
except Exception as e:
diff --git a/main/xiaozhi-server/plugins_func/functions/web_search.py b/main/xiaozhi-server/plugins_func/functions/web_search.py
index 6411a0ed..519f8b2b 100644
--- a/main/xiaozhi-server/plugins_func/functions/web_search.py
+++ b/main/xiaozhi-server/plugins_func/functions/web_search.py
@@ -1,4 +1,4 @@
-import requests
+import httpx
from config.logger import setup_logging
from plugins_func.register import (
register_function,
@@ -37,7 +37,7 @@ WEB_SEARCH_FUNCTION_DESC = {
}
-def _search_metaso(api_key: str, query: str, max_results: int) -> str:
+async def _search_metaso(api_key: str, query: str, max_results: int) -> str:
"""调用秘塔搜索API"""
url = "https://metaso.cn/api/v1/search"
headers = {
@@ -54,8 +54,8 @@ def _search_metaso(api_key: str, query: str, max_results: int) -> str:
"conciseSnippet": False,
}
logger.bind(tag=TAG).debug(f"秘塔搜索请求 | URL: {url} | payload: {payload}")
- response = requests.post(url, json=payload, headers=headers, timeout=15)
- response.raise_for_status()
+ async with httpx.AsyncClient(timeout=httpx.Timeout(15.0, connect=3.0)) as client:
+ response = await client.post(url, json=payload, headers=headers)
data = response.json()
logger.bind(tag=TAG).debug(f"秘塔搜索响应 | status: {response.status_code}")
@@ -77,7 +77,7 @@ def _search_metaso(api_key: str, query: str, max_results: int) -> str:
return "\n".join(lines)
-def _search_tavily(api_key: str, query: str, max_results: int) -> str:
+async def _search_tavily(api_key: str, query: str, max_results: int) -> str:
"""调用Tavily搜索API"""
url = "https://api.tavily.com/search"
headers = {
@@ -91,8 +91,8 @@ def _search_tavily(api_key: str, query: str, max_results: int) -> str:
"include_answer": "advanced",
}
logger.bind(tag=TAG).debug(f"Tavily搜索请求 | URL: {url} | payload: {payload}")
- response = requests.post(url, json=payload, headers=headers, timeout=15)
- response.raise_for_status()
+ async with httpx.AsyncClient(timeout=httpx.Timeout(15.0, connect=3.0)) as client:
+ response = await client.post(url, json=payload, headers=headers)
data = response.json()
logger.bind(tag=TAG).debug(f"Tavily搜索响应 | status: {response.status_code} | data: {data}")
@@ -113,7 +113,7 @@ def _search_tavily(api_key: str, query: str, max_results: int) -> str:
@register_function("web_search", WEB_SEARCH_FUNCTION_DESC, ToolType.SYSTEM_CTL)
-def web_search(conn: "ConnectionHandler", query: str = None):
+async def web_search(conn: "ConnectionHandler", query: str = None):
logger.bind(tag=TAG).info(f"web_search 被调用 | query={query}")
if not query:
return ActionResponse(Action.REQLLM, "请提供搜索关键词。", None)
@@ -131,24 +131,22 @@ def web_search(conn: "ConnectionHandler", query: str = None):
None,
)
- if provider == "metaso":
- search_fn = lambda: _search_metaso(api_key, query, max_results)
- elif provider == "tavily":
- search_fn = lambda: _search_tavily(api_key, query, max_results)
- else:
- return ActionResponse(
- Action.REQLLM,
- f"联网搜索功能未配置或配置的搜索源无效(当前:{provider}),请检查配置。",
- None,
- )
-
try:
- result_text = search_fn()
+ if provider == "metaso":
+ result_text = await _search_metaso(api_key, query, max_results)
+ elif provider == "tavily":
+ result_text = await _search_tavily(api_key, query, max_results)
+ else:
+ return ActionResponse(
+ Action.REQLLM,
+ f"联网搜索功能未配置或配置的搜索源无效(当前:{provider}),请检查配置。",
+ None,
+ )
logger.bind(tag=TAG).info(f"搜索结果组装完成:\n{result_text}")
- except requests.exceptions.Timeout:
+ except httpx.TimeoutException:
logger.bind(tag=TAG).error("联网搜索请求超时")
result_text = "联网搜索请求超时,请稍后重试。"
- except requests.exceptions.RequestException as e:
+ except httpx.HTTPStatusError as e:
logger.bind(tag=TAG).error(f"联网搜索请求失败: {e}")
result_text = "联网搜索请求失败,请稍后重试。"
except Exception as e: