mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 01:23:55 +08:00
update:优化代码
This commit is contained in:
+37
-10
@@ -1,5 +1,6 @@
|
|||||||
package xiaozhi.modules.agent.service.impl;
|
package xiaozhi.modules.agent.service.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -7,11 +8,11 @@ import java.util.Map;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.alibaba.druid.support.json.JSONUtils;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
import xiaozhi.modules.agent.dao.AgentPluginMappingMapper;
|
import xiaozhi.modules.agent.dao.AgentPluginMappingMapper;
|
||||||
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
||||||
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
||||||
@@ -35,7 +36,8 @@ public class AgentPluginMappingServiceImpl extends ServiceImpl<AgentPluginMappin
|
|||||||
@Override
|
@Override
|
||||||
public List<AgentPluginMapping> agentPluginParamsByAgentId(String agentId) {
|
public List<AgentPluginMapping> agentPluginParamsByAgentId(String agentId) {
|
||||||
List<AgentPluginMapping> list = agentPluginMappingMapper.selectPluginsByAgentId(agentId);
|
List<AgentPluginMapping> list = agentPluginMappingMapper.selectPluginsByAgentId(agentId);
|
||||||
int index = 0;
|
Map<String, List<KnowledgeBaseEntity>> knowledgeBaseMap = new HashMap<>();
|
||||||
|
Map<String, ModelConfigEntity> modelConfigMap = new HashMap<>();
|
||||||
for (int i = list.size() - 1; i >= 0; i--) {
|
for (int i = list.size() - 1; i >= 0; i--) {
|
||||||
AgentPluginMapping mapping = list.get(i);
|
AgentPluginMapping mapping = list.get(i);
|
||||||
if (StringUtils.isBlank(mapping.getProviderCode())) {
|
if (StringUtils.isBlank(mapping.getProviderCode())) {
|
||||||
@@ -51,14 +53,39 @@ public class AgentPluginMappingServiceImpl extends ServiceImpl<AgentPluginMappin
|
|||||||
list.remove(i);
|
list.remove(i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Map<String, String> paramInfo = new HashMap<>(2);
|
List<KnowledgeBaseEntity> knowledgeBaseList = knowledgeBaseMap.get(modelConfigEntity.getModelCode());
|
||||||
paramInfo.put("name", knowledgeBaseEntity.getName());
|
if (knowledgeBaseList == null) {
|
||||||
paramInfo.put("description", knowledgeBaseEntity.getDescription());
|
knowledgeBaseList = new ArrayList<>();
|
||||||
mapping.setParamInfo(JSONUtils.toJSONString(paramInfo));
|
}
|
||||||
String providerCode = "xzKnowledgeBase_search_from_" + modelConfigEntity.getModelCode() + "_"
|
modelConfigMap.put(modelConfigEntity.getModelCode(), modelConfigEntity);
|
||||||
+ index;
|
knowledgeBaseList.add(knowledgeBaseEntity);
|
||||||
index++;
|
knowledgeBaseMap.put(modelConfigEntity.getModelCode(), knowledgeBaseList);
|
||||||
mapping.setProviderCode(providerCode);
|
list.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (knowledgeBaseMap.size() > 0) {
|
||||||
|
for (String pluginCode : knowledgeBaseMap.keySet()) {
|
||||||
|
List<KnowledgeBaseEntity> knowledgeBaseList = knowledgeBaseMap.get(pluginCode);
|
||||||
|
if (knowledgeBaseList == null || knowledgeBaseList.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AgentPluginMapping agentPluginMapping = new AgentPluginMapping();
|
||||||
|
agentPluginMapping.setAgentId(agentId);
|
||||||
|
agentPluginMapping.setPluginId(pluginCode);
|
||||||
|
agentPluginMapping.setProviderCode("search_from_" + pluginCode);
|
||||||
|
agentPluginMapping.setId(Long.valueOf(list.size() + 1));
|
||||||
|
|
||||||
|
Map<String, Object> paramInfo = new HashMap<>(4);
|
||||||
|
ModelConfigEntity modelConfigEntity = modelConfigMap.get(pluginCode);
|
||||||
|
paramInfo.put("base_url", modelConfigEntity.getConfigJson().getStr("base_url"));
|
||||||
|
paramInfo.put("api_key", modelConfigEntity.getConfigJson().getStr("api_key"));
|
||||||
|
paramInfo.put("dataset_ids",
|
||||||
|
knowledgeBaseList.stream().map(KnowledgeBaseEntity::getDatasetId).toList());
|
||||||
|
paramInfo.put("description",
|
||||||
|
String.join(",", knowledgeBaseList.stream().map(KnowledgeBaseEntity::getDescription).toList()));
|
||||||
|
agentPluginMapping.setParamInfo(JsonUtils.toJsonString(paramInfo));
|
||||||
|
list.add(agentPluginMapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
|||||||
@@ -147,7 +147,15 @@ plugins:
|
|||||||
- ".wav"
|
- ".wav"
|
||||||
- ".p3"
|
- ".p3"
|
||||||
refresh_time: 300 # 刷新音乐列表的时间间隔,单位为秒
|
refresh_time: 300 # 刷新音乐列表的时间间隔,单位为秒
|
||||||
|
search_from_ragflow:
|
||||||
|
# 知识库的描述信息,方便大语言模型知道什么时候调用
|
||||||
|
description: "当用户问xxx时,调用本方法,使用知识库中的信息回答问题"
|
||||||
|
# ragflow接口配置
|
||||||
|
base_url: "http://192.168.0.8"
|
||||||
|
# ragflow api访问令牌
|
||||||
|
api_key: "ragflow-xxx"
|
||||||
|
# ragflow知识库id
|
||||||
|
dataset_ids: ["123456789"]
|
||||||
# 声纹识别配置
|
# 声纹识别配置
|
||||||
voiceprint:
|
voiceprint:
|
||||||
# 声纹接口地址
|
# 声纹接口地址
|
||||||
@@ -239,6 +247,7 @@ Intent:
|
|||||||
functions:
|
functions:
|
||||||
- change_role
|
- change_role
|
||||||
- get_weather
|
- get_weather
|
||||||
|
# - search_from_ragflow
|
||||||
# - get_news_from_chinanews
|
# - get_news_from_chinanews
|
||||||
- get_news_from_newsnow
|
- get_news_from_newsnow
|
||||||
# play_music是服务器自带的音乐播放,hass_play_music是通过home assistant控制的独立外部程序音乐播放
|
# play_music是服务器自带的音乐播放,hass_play_music是通过home assistant控制的独立外部程序音乐播放
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""服务端插件工具执行器"""
|
"""服务端插件工具执行器"""
|
||||||
|
|
||||||
from typing import Dict, Any, Optional, Tuple
|
from typing import Dict, Any
|
||||||
from ..base import ToolType, ToolDefinition, ToolExecutor
|
from ..base import ToolType, ToolDefinition, ToolExecutor
|
||||||
from plugins_func.register import all_function_registry, Action, ActionResponse
|
from plugins_func.register import all_function_registry, Action, ActionResponse
|
||||||
|
|
||||||
@@ -11,53 +11,12 @@ class ServerPluginExecutor(ToolExecutor):
|
|||||||
def __init__(self, conn):
|
def __init__(self, conn):
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
self.config = conn.config
|
self.config = conn.config
|
||||||
# 存储知识库工具名称到真实插件名称的映射
|
|
||||||
self._knowledge_base_mapping: Dict[str, str] = {}
|
|
||||||
|
|
||||||
def _parse_knowledge_base_config(self, config_name: str) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
解析知识库配置名称,提取真实的插件名称
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config_name: 配置名称,格式为 xzKnowledgeBase_<plugin_name>_<index>
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
真实的插件名称,如果不是知识库配置则返回 None
|
|
||||||
|
|
||||||
Example:
|
|
||||||
"xzKnowledgeBase_search_from_ragflow_0" -> "search_from_ragflow"
|
|
||||||
"xzKnowledgeBase_search_from_ragflow_1" -> "search_from_ragflow"
|
|
||||||
"""
|
|
||||||
if not config_name.startswith("xzKnowledgeBase_"):
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 移除前缀
|
|
||||||
name_without_prefix = config_name[len("xzKnowledgeBase_"):]
|
|
||||||
|
|
||||||
# 找到最后一个下划线的位置
|
|
||||||
last_underscore_index = name_without_prefix.rfind("_")
|
|
||||||
|
|
||||||
if last_underscore_index == -1:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 提取真实插件名称(从开头到最后一个下划线之前)
|
|
||||||
real_plugin_name = name_without_prefix[:last_underscore_index]
|
|
||||||
|
|
||||||
return real_plugin_name
|
|
||||||
|
|
||||||
async def execute(
|
async def execute(
|
||||||
self, conn, tool_name: str, arguments: Dict[str, Any]
|
self, conn, tool_name: str, arguments: Dict[str, Any]
|
||||||
) -> ActionResponse:
|
) -> ActionResponse:
|
||||||
"""执行服务端插件工具"""
|
"""执行服务端插件工具"""
|
||||||
# 检查是否是知识库工具调用
|
func_item = all_function_registry.get(tool_name)
|
||||||
real_plugin_name = self._knowledge_base_mapping.get(tool_name)
|
|
||||||
if real_plugin_name:
|
|
||||||
# 使用真实的插件名称获取函数
|
|
||||||
func_item = all_function_registry.get(real_plugin_name)
|
|
||||||
else:
|
|
||||||
# 普通插件调用
|
|
||||||
func_item = all_function_registry.get(tool_name)
|
|
||||||
|
|
||||||
if not func_item:
|
if not func_item:
|
||||||
return ActionResponse(
|
return ActionResponse(
|
||||||
action=Action.NOTFOUND, response=f"插件函数 {tool_name} 不存在"
|
action=Action.NOTFOUND, response=f"插件函数 {tool_name} 不存在"
|
||||||
@@ -112,51 +71,27 @@ class ServerPluginExecutor(ToolExecutor):
|
|||||||
for func_name in all_required_functions:
|
for func_name in all_required_functions:
|
||||||
func_item = all_function_registry.get(func_name)
|
func_item = all_function_registry.get(func_name)
|
||||||
if func_item:
|
if func_item:
|
||||||
|
# 从函数注册中获取描述
|
||||||
|
fun_description = (
|
||||||
|
self.config.get("plugins", {})
|
||||||
|
.get(func_name, {})
|
||||||
|
.get("description", "")
|
||||||
|
)
|
||||||
|
if fun_description is not None and len(fun_description) > 0:
|
||||||
|
if "function" in func_item.description and isinstance(
|
||||||
|
func_item.description["function"], dict
|
||||||
|
):
|
||||||
|
func_item.description["function"][
|
||||||
|
"description"
|
||||||
|
] = fun_description
|
||||||
tools[func_name] = ToolDefinition(
|
tools[func_name] = ToolDefinition(
|
||||||
name=func_name,
|
name=func_name,
|
||||||
description=func_item.description,
|
description=func_item.description,
|
||||||
tool_type=ToolType.SERVER_PLUGIN,
|
tool_type=ToolType.SERVER_PLUGIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 处理知识库配置
|
|
||||||
plugins_config = self.config.get("plugins", {})
|
|
||||||
for config_name, config_value in plugins_config.items():
|
|
||||||
# 检查是否是知识库配置
|
|
||||||
real_plugin_name = self._parse_knowledge_base_config(config_name)
|
|
||||||
if real_plugin_name:
|
|
||||||
# 获取真实的插件函数
|
|
||||||
func_item = all_function_registry.get(real_plugin_name)
|
|
||||||
if func_item and isinstance(config_value, dict):
|
|
||||||
# 从配置中获取自定义的 name 和 description
|
|
||||||
custom_name = config_value.get("name", "")
|
|
||||||
custom_description = config_value.get("description", "")
|
|
||||||
|
|
||||||
# 创建动态的工具名称(使用配置名称去掉前缀部分作为工具名)
|
|
||||||
tool_name = config_name[len("xzKnowledgeBase_"):]
|
|
||||||
|
|
||||||
# 复制原始函数描述并修改
|
|
||||||
custom_func_desc = func_item.description.copy()
|
|
||||||
if "function" in custom_func_desc:
|
|
||||||
custom_func_desc["function"] = custom_func_desc["function"].copy()
|
|
||||||
custom_func_desc["function"]["name"] = tool_name
|
|
||||||
custom_func_desc["function"]["description"] = custom_description
|
|
||||||
|
|
||||||
# 注册工具
|
|
||||||
tools[tool_name] = ToolDefinition(
|
|
||||||
name=tool_name,
|
|
||||||
description=custom_func_desc,
|
|
||||||
tool_type=ToolType.SERVER_PLUGIN,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 保存映射关系
|
|
||||||
self._knowledge_base_mapping[tool_name] = real_plugin_name
|
|
||||||
|
|
||||||
return tools
|
return tools
|
||||||
|
|
||||||
def has_tool(self, tool_name: str) -> bool:
|
def has_tool(self, tool_name: str) -> bool:
|
||||||
"""检查是否有指定的服务端插件工具"""
|
"""检查是否有指定的服务端插件工具"""
|
||||||
# 检查是否是知识库工具
|
|
||||||
if tool_name in self._knowledge_base_mapping:
|
|
||||||
return True
|
|
||||||
# 检查是否是普通工具
|
|
||||||
return tool_name in all_function_registry
|
return tool_name in all_function_registry
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
from config.logger import setup_logging
|
||||||
from plugins_func.register import register_function, ToolType, ActionResponse, Action
|
from plugins_func.register import register_function, ToolType, ActionResponse, Action
|
||||||
|
|
||||||
|
TAG = __name__
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
# 定义基础的函数描述模板
|
# 定义基础的函数描述模板
|
||||||
SEARCH_FROM_RAGFLOW_FUNCTION_DESC = {
|
SEARCH_FROM_RAGFLOW_FUNCTION_DESC = {
|
||||||
"type": "function",
|
"type": "function",
|
||||||
@@ -8,26 +14,85 @@ SEARCH_FROM_RAGFLOW_FUNCTION_DESC = {
|
|||||||
"description": "从知识库中查询信息",
|
"description": "从知识库中查询信息",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {"query": {"type": "string", "description": "查询的关键词"}},
|
"properties": {"question": {"type": "string", "description": "查询的问题"}},
|
||||||
"required": ["query"],
|
"required": ["question"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@register_function(
|
@register_function(
|
||||||
"search_from_ragflow", SEARCH_FROM_RAGFLOW_FUNCTION_DESC, ToolType.WAIT
|
"search_from_ragflow", SEARCH_FROM_RAGFLOW_FUNCTION_DESC, ToolType.SYSTEM_CTL
|
||||||
)
|
)
|
||||||
def search_from_ragflow(query=None):
|
def search_from_ragflow(conn, question=None):
|
||||||
"""
|
# 确保字符串参数正确处理编码
|
||||||
用于从ragflow知识库中查询信息
|
if question and isinstance(question, str):
|
||||||
"""
|
# 确保问题参数是UTF-8编码的字符串
|
||||||
# TODO 从ragflow知识库中查询信息
|
pass
|
||||||
if query and "医生" in query:
|
|
||||||
response_text = "医院有张山、里斯、王五3名全科医生,其中王五医生是主要擅长眼科"
|
|
||||||
elif query and "科室" in query:
|
|
||||||
response_text = "医院眼科、麻醉科"
|
|
||||||
else:
|
else:
|
||||||
response_text = "暂无相关信息"
|
question = str(question) if question is not None else ""
|
||||||
|
|
||||||
return ActionResponse(Action.REQLLM, response_text, None)
|
base_url = conn.config["plugins"]["search_from_ragflow"].get("base_url", "")
|
||||||
|
api_key = conn.config["plugins"]["search_from_ragflow"].get("api_key", "")
|
||||||
|
dataset_ids = conn.config["plugins"]["search_from_ragflow"].get("dataset_ids", [])
|
||||||
|
|
||||||
|
url = base_url + "/api/v1/retrieval"
|
||||||
|
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
||||||
|
|
||||||
|
# 确保payload中的字符串都是UTF-8编码
|
||||||
|
payload = {"question": question, "dataset_ids": dataset_ids}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 使用ensure_ascii=False确保JSON序列化时正确处理中文
|
||||||
|
response = requests.post(
|
||||||
|
url,
|
||||||
|
json=payload,
|
||||||
|
headers=headers,
|
||||||
|
timeout=5,
|
||||||
|
verify=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 显式设置响应的编码为utf-8
|
||||||
|
response.encoding = "utf-8"
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# 先获取文本内容,然后手动处理JSON解码
|
||||||
|
response_text = response.text
|
||||||
|
import json
|
||||||
|
|
||||||
|
result = json.loads(response_text)
|
||||||
|
|
||||||
|
if result.get("code") != 0:
|
||||||
|
error_detail = response.get("error", {}).get("detail", "")
|
||||||
|
# 安全地记录错误信息
|
||||||
|
logger.bind(tag=TAG).error(
|
||||||
|
"从RAGflow获取信息失败,原因:%s", str(error_detail)
|
||||||
|
)
|
||||||
|
return ActionResponse(Action.RESPONSE, None, "RAG接口返回异常")
|
||||||
|
|
||||||
|
chunks = result.get("data", {}).get("chunks", [])
|
||||||
|
contents = []
|
||||||
|
for chunk in chunks:
|
||||||
|
content = chunk.get("content", "")
|
||||||
|
if content:
|
||||||
|
# 安全地处理内容字符串
|
||||||
|
if isinstance(content, str):
|
||||||
|
contents.append(content)
|
||||||
|
elif isinstance(content, bytes):
|
||||||
|
contents.append(content.decode("utf-8", errors="replace"))
|
||||||
|
else:
|
||||||
|
contents.append(str(content))
|
||||||
|
|
||||||
|
# 构建适合大模型的上下文内容(每段前加编号,段间两个换行)
|
||||||
|
context_text = "\n\n".join(
|
||||||
|
f"{i+1}. {c.strip()}" for i, c in enumerate(contents[:5])
|
||||||
|
)
|
||||||
|
if not context_text:
|
||||||
|
context_text = "根据知识库查询结果,没有相关信息。"
|
||||||
|
return ActionResponse(Action.REQLLM, context_text, None)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# 使用安全的方式记录异常,避免编码问题
|
||||||
|
logger.bind(tag=TAG).error("从RAGflow获取信息失败,原因:%s", str(e))
|
||||||
|
return ActionResponse(Action.RESPONSE, None, "RAG接口返回异常")
|
||||||
|
|||||||
Reference in New Issue
Block a user