Compare commits

...
5 Commits
Author SHA1 Message Date
2b662d3a14 fix:function call bug (#268)
* 优化function call消息处理

* fix:用户说话重复记录bug

* 2025-3-10-优化llm intent (#254)

Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com>

* update:回复上版提示词,无需再二次识别歌曲名

---------

Co-authored-by: 玄凤科技 <eric230308@gmail.com>
Co-authored-by: hrz <1710360675@qq.com>
Co-authored-by: Jiao Haoyang <108573524+XuSenfeng@users.noreply.github.com>
2025-03-11 00:25:33 +08:00
4f3fae81c1 fix:修复长时间不说话关闭意图bug (#267)
Co-authored-by: hrz <1710360675@qq.com>
2025-03-10 22:45:03 +08:00
7b266ad7c0 完成搜索功能开发 (#263)
Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com>
2025-03-10 18:29:08 +08:00
CGDandGitHub 16601a67e3 完成解绑功能 (#264) 2025-03-10 18:21:59 +08:00
224d59d50d fix:声音卡顿问题 (#262)
Co-authored-by: hrz <1710360675@qq.com>
2025-03-10 15:56:28 +08:00
14 changed files with 200 additions and 134 deletions
+7 -7
View File
@@ -93,7 +93,7 @@ docker logs -f xiaozhi-esp32-server
``` ```
docker stop xiaozhi-esp32-server docker stop xiaozhi-esp32-server
docker rm xiaozhi-esp32-server docker rm xiaozhi-esp32-server
docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:latest docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
``` ```
3、重新按docker方式部署 3、重新按docker方式部署
@@ -299,14 +299,14 @@ LLM:
以下是一些常见问题,供参考: 以下是一些常见问题,供参考:
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [1、为什么我说的话,小智识别出来很多韩文、日文、英文](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
[2、为什么会出现“TTS 任务出错 文件不存在”?](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [2、为什么会出现“TTS 任务出错 文件不存在”?](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
[3、TTS 经常失败,经常超时](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [3、TTS 经常失败,经常超时](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
[4、如何提高小智对话响应速度?](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [4、如何提高小智对话响应速度?](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
[5、我说话很慢,停顿时小智老是抢话](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [5、我说话很慢,停顿时小智老是抢话](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
[6、我想通过小智控制电灯、空调、远程开关机等操作](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [6、我想通过小智控制电灯、空调、远程开关机等操作](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
+15
View File
@@ -43,4 +43,19 @@ export default {
}) })
}).send() }).send()
}, },
// 解绑设备
unbindDevice(device_id, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/user/device/unbind/${device_id}`)
.method('PUT')
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.fail(() => {
RequestService.reAjaxFun(() => {
this.unbindDevice(device_id, callback);
});
}).send()
},
} }
+3
View File
@@ -1,6 +1,9 @@
const HAVE_NO_RESULT = '暂无' const HAVE_NO_RESULT = '暂无'
export default { export default {
HAVE_NO_RESULT, // 项目的配置信息 HAVE_NO_RESULT, // 项目的配置信息
PAGE: {
LOGIN: '/login',
},
STORAGE_KEY: { STORAGE_KEY: {
TOKEN: 'TOKEN', TOKEN: 'TOKEN',
PUBLIC_KEY: 'PUBLIC_KEY', PUBLIC_KEY: 'PUBLIC_KEY',
+52 -14
View File
@@ -21,9 +21,9 @@
</div> </div>
<div style="display: flex;align-items: center;gap: 8px;margin-top: 10px"> <div style="display: flex;align-items: center;gap: 8px;margin-top: 10px">
<div class="serach-box"> <div class="serach-box">
<el-input placeholder="输入名称搜索.." v-model="serach" style="border: none; background: transparent;" /> <el-input placeholder="输入名称搜索.." v-model="serach" style="border: none; background: transparent;" @keyup.enter.native="handleSearch" />
<img src="@/assets/home/search.png" alt="" <img src="@/assets/home/search.png" alt=""
style="width: 12px;height: 12px;margin-right: 11px;cursor: pointer;" /> style="width: 12px;height: 12px;margin-right: 11px;cursor: pointer;" @click="handleSearch" />
</div> </div>
<img src="@/assets/home/avatar.png" alt="" style="width: 21px;height: 21px;" /> <img src="@/assets/home/avatar.png" alt="" style="width: 21px;height: 21px;" />
<div class="user-info"> <div class="user-info">
@@ -56,16 +56,16 @@
</div> </div>
</div> </div>
<div <div
style="display: flex;flex-wrap: wrap;margin-top: 15px;gap: 15px;justify-content: space-between;box-sizing: border-box;"> style="display: flex;flex-wrap: wrap;margin-top: 15px;gap: 15px;justify-content: flex-start;box-sizing: border-box;">
<div class="device-item" v-for="(item,index) in deviceList" :key="index"> <div class="device-item" v-for="(item,index) in filteredDeviceList" :key="index">
<div style="display: flex;justify-content: space-between;"> <div style="display: flex;justify-content: space-between; align-items: center; ">
<div style="font-weight: 700;font-size: 18px;text-align: left;color: #3d4566;"> <div style="font-weight: 700;font-size: 18px;text-align: left;color: #3d4566;">
<!-- CC:ba:97:11:a6:ac--> <!-- CC:ba:97:11:a6:ac-->
{{item.list[0]?.mac_address}} {{item.list[0]?.mac_address}}
</div> </div>
<div> <div style="display: flex;align-items: center;">
<img src="@/assets/home/delete.png" alt="" <img src="@/assets/home/delete.png" alt=""
style="width: 18px;height: 18px;margin-right: 8px;" /> style="width: 18px;height: 18px;margin-right: 8px;" @click="unbindDevice(item.list[0]?.id)" />
<img src="@/assets/home/info.png" alt="" style="width: 18px;height: 18px;" /> <img src="@/assets/home/info.png" alt="" style="width: 18px;height: 18px;" />
</div> </div>
</div> </div>
@@ -229,7 +229,9 @@ export default {
name: 'home', name: 'home',
data() { data() {
return { return {
serach: '', serach: '', // 搜索框输入内容
deviceList: [], // 原始设备列表
filteredDeviceList: [], // 过滤后的设备列表
switchValue: false, switchValue: false,
addDeviceDialogVisible: false, addDeviceDialogVisible: false,
deviceCode: "", deviceCode: "",
@@ -250,8 +252,7 @@ export default {
}], }],
userInfo: { userInfo: {
mobile: '' // 初始化用户信息 mobile: '' // 初始化用户信息
}, }
deviceList:[]
}; };
}, },
methods: { methods: {
@@ -270,14 +271,51 @@ export default {
// 获取已绑设备 // 获取已绑设备
getList(){ getList(){
Api.user.getHomeList(({data})=>{ Api.user.getHomeList(({data})=>{
console.log(data.data) this.deviceList = data.data; // 保存原始设备列表
this.deviceList = data.data this.filteredDeviceList = data.data; // 初始化过滤后的设备列表
}) })
} },
// 处理搜索
handleSearch() {
if (this.serach.trim() === '') {
// 如果搜索框为空,显示全部设备
this.filteredDeviceList = this.deviceList;
} else {
// 过滤设备列表
this.filteredDeviceList = this.deviceList.filter(device => {
return (
device.list[0]?.mac_address?.includes(this.serach) || // 匹配MAC地址
device.list[0]?.device_type?.includes(this.serach) || // 匹配设备型号
device.list[0]?.app_version?.includes(this.serach) // 匹配APP版本
);
});
}
},
// 解绑设备
unbindDevice(device_id) {
this.$confirm('确定要解绑该设备吗', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 调用解绑设备的接口
Api.user.unbindDevice(device_id, ({ data }) => {
if (data.code === 0) {
this.$message.success('解绑成功');
this.getList();
} else {
this.$message.error(data.msg || '解绑失败');
}
});
}).catch(() => {
// 用户取消操作
this.$message.info('已取消解绑');
});
},
}, },
mounted() { mounted() {
this.fetchUserInfo(); // 组件加载时获取用户信息 this.fetchUserInfo(); // 组件加载时获取用户信息
this.getList() this.getList(); // 初始化设备列表
} }
} }
</script> </script>
+81 -46
View File
@@ -11,7 +11,7 @@ import websockets
from typing import Dict, Any from typing import Dict, Any
from core.utils.dialogue import Message, Dialogue from core.utils.dialogue import Message, Dialogue
from core.handle.textHandle import handleTextMessage from core.handle.textHandle import handleTextMessage
from core.utils.util import get_string_no_punctuation_or_emoji from core.utils.util import get_string_no_punctuation_or_emoji, extract_json_from_string
from concurrent.futures import ThreadPoolExecutor, TimeoutError from concurrent.futures import ThreadPoolExecutor, TimeoutError
from core.handle.sendAudioHandle import sendAudioMessage from core.handle.sendAudioHandle import sendAudioMessage
from core.handle.receiveAudioHandle import handleAudioMessage from core.handle.receiveAudioHandle import handleAudioMessage
@@ -307,7 +307,6 @@ class ConnectionHandler:
response_message = [] response_message = []
processed_chars = 0 # 跟踪已处理的字符位置 processed_chars = 0 # 跟踪已处理的字符位置
function_call_data = None # 存储function call数据
try: try:
start_time = time.time() start_time = time.time()
@@ -316,7 +315,7 @@ class ConnectionHandler:
future = asyncio.run_coroutine_threadsafe(self.memory.query_memory(query), self.loop) future = asyncio.run_coroutine_threadsafe(self.memory.query_memory(query), self.loop)
memory_str = future.result() memory_str = future.result()
# self.logger.bind(tag=TAG).info(f"记忆内容: {memory_str}") #self.logger.bind(tag=TAG).info(f"对话记录: {self.dialogue.get_llm_dialogue_with_memory(memory_str)}")
# 使用支持functions的streaming接口 # 使用支持functions的streaming接口
llm_responses = self.llm.response_with_functions( llm_responses = self.llm.response_with_functions(
@@ -332,48 +331,61 @@ class ConnectionHandler:
text_index = 0 text_index = 0
# 处理流式响应 # 处理流式响应
tool_call_flag = False
function_name = None
function_id = None
function_arguments = ""
content_arguments = ""
for response in llm_responses: for response in llm_responses:
if response["type"] == "content": content, tools_call = response
content = response["content"] if content is not None and len(content)>0:
response_message.append(content) if len(response_message)<=0 and content=="```":
tool_call_flag = True
if self.client_abort: if tools_call is not None:
break tool_call_flag = True
if tools_call[0].id is not None:
function_id = tools_call[0].id
if tools_call[0].function.name is not None:
function_name = tools_call[0].function.name
if tools_call[0].function.arguments is not None:
function_arguments += tools_call[0].function.arguments
end_time = time.time() if content is not None and len(content) > 0:
self.logger.bind(tag=TAG).debug(f"大模型返回时间: {end_time - start_time} 秒, 生成token={content}") if tool_call_flag:
content_arguments+=content
else:
response_message.append(content)
# 处理文本分段和TTS逻辑 if self.client_abort:
# 合并当前全部文本并处理未分割部分 break
full_text = "".join(response_message)
current_text = full_text[processed_chars:] # 从未处理的位置开始
# 查找最后一个有效标点 end_time = time.time()
punctuations = ("", "", "", "", "") self.logger.bind(tag=TAG).debug(f"大模型返回时间: {end_time - start_time} 秒, 生成token={content}")
last_punct_pos = -1
for punct in punctuations:
pos = current_text.rfind(punct)
if pos > last_punct_pos:
last_punct_pos = pos
# 找到分割点则处理 # 处理文本分段和TTS逻辑
if last_punct_pos != -1: # 合并当前全部文本并处理未分割部分
segment_text_raw = current_text[:last_punct_pos + 1] full_text = "".join(response_message)
segment_text = get_string_no_punctuation_or_emoji(segment_text_raw) current_text = full_text[processed_chars:] # 从未处理的位置开始
if segment_text:
text_index += 1
self.recode_first_last_text(segment_text, text_index)
future = self.executor.submit(self.speak_and_play, segment_text, text_index)
self.tts_queue.put(future)
processed_chars += len(segment_text_raw) # 更新已处理字符位置
elif response["type"] == "function_call": # 查找最后一个有效标点
# Extract function call data punctuations = ("", "", "", "", "")
function_call_data = { last_punct_pos = -1
"name": response["function_call"]["function"]["name"], for punct in punctuations:
"arguments": response["function_call"]["function"]["arguments"] pos = current_text.rfind(punct)
} if pos > last_punct_pos:
self.logger.bind(tag=TAG).info(f"Function call detected: {function_call_data}") last_punct_pos = pos
# 找到分割点则处理
if last_punct_pos != -1:
segment_text_raw = current_text[:last_punct_pos + 1]
segment_text = get_string_no_punctuation_or_emoji(segment_text_raw)
if segment_text:
text_index += 1
self.recode_first_last_text(segment_text, text_index)
future = self.executor.submit(self.speak_and_play, segment_text, text_index)
self.tts_queue.put(future)
processed_chars += len(segment_text_raw) # 更新已处理字符位置
# 处理最后剩余的文本 # 处理最后剩余的文本
full_text = "".join(response_message) full_text = "".join(response_message)
@@ -387,23 +399,46 @@ class ConnectionHandler:
self.tts_queue.put(future) self.tts_queue.put(future)
# 存储对话内容 # 存储对话内容
self.dialogue.put(Message(role="assistant", content="".join(response_message))) if len(response_message)>0:
self.dialogue.put(Message(role="assistant", content="".join(response_message)))
# 处理function call # 处理function call
if function_call_data: if tool_call_flag:
if function_id is None:
a = extract_json_from_string(content_arguments)
if a is not None:
content_arguments_json = json.loads(a)
function_name = content_arguments_json["function_name"]
function_arguments = json.dumps(content_arguments_json["args"], ensure_ascii=False)
function_id = str(uuid.uuid4().hex)
else:
return []
function_arguments = json.loads(function_arguments)
self.logger.bind(tag=TAG).info(f"function_name={function_name}, function_id={function_id}, function_arguments={function_arguments}")
function_call_data = {
"name": function_name,
"id": function_id,
"arguments": function_arguments
}
result = handle_llm_function_call(self, function_call_data) result = handle_llm_function_call(self, function_call_data)
if result.action == Action.RESPONSE: self._handle_function_result(result, function_call_data, text_index+1)
text = result.response
text_index += 1
self.recode_first_last_text(text, text_index)
future = self.executor.submit(self.speak_and_play, text, text_index)
self.tts_queue.put(future)
self.llm_finish_task = True self.llm_finish_task = True
self.logger.bind(tag=TAG).debug(json.dumps(self.dialogue.get_llm_dialogue(), indent=4, ensure_ascii=False)) self.logger.bind(tag=TAG).debug(json.dumps(self.dialogue.get_llm_dialogue(), indent=4, ensure_ascii=False))
return True return True
def _handle_function_result(self, result, function_call_data, text_index):
if result.action == Action.RESPONSE: # 直接回复前端
text = result.response
self.recode_first_last_text(text, text_index)
future = self.executor.submit(self.speak_and_play, text, text_index)
self.tts_queue.put(future)
self.dialogue.put(Message(role="assistant", content=text))
if result.action == Action.REQLLM: # 调用函数后再请求llm生成回复
text = result.response
def _tts_priority_thread(self): def _tts_priority_thread(self):
while not self.stop_event.is_set(): while not self.stop_event.is_set():
text = None text = None
@@ -122,12 +122,10 @@ async def analyze_intent_with_llm(conn, text):
logger.bind(tag=TAG).warning("意图识别服务未初始化") logger.bind(tag=TAG).warning("意图识别服务未初始化")
return None return None
# 创建对话历史记录 # 对话历史记录
dialogue = conn.dialogue dialogue = conn.dialogue
dialogue.put(Message(role="user", content=text))
try: try:
intent_result = await conn.intent.detect_intent(dialogue.dialogue) intent_result = await conn.intent.detect_intent(dialogue.dialogue, text)
logger.bind(tag=TAG).info(f"意图识别结果: {intent_result}") logger.bind(tag=TAG).info(f"意图识别结果: {intent_result}")
# 尝试解析JSON结果 # 尝试解析JSON结果
@@ -67,8 +67,9 @@ async def no_voice_close_connect(conn):
else: else:
no_voice_time = time.time() * 1000 - conn.client_no_voice_last_time no_voice_time = time.time() * 1000 - conn.client_no_voice_last_time
close_connection_no_voice_time = conn.config.get("close_connection_no_voice_time", 120) close_connection_no_voice_time = conn.config.get("close_connection_no_voice_time", 120)
if no_voice_time > 1000 * close_connection_no_voice_time: if not conn.close_after_chat and no_voice_time > 1000 * close_connection_no_voice_time:
conn.close_after_chat = True
conn.client_abort = False conn.client_abort = False
conn.asr_server_receive = False conn.asr_server_receive = False
prompt = "时间过得真快,我都好久没说话了。请你用十个字左右话跟我告别,以“再见”或“拜拜”为结尾" prompt = "请你以“时间过得真快”未来头,用富有感情、依依不舍的话来结束这场对话吧。"
await startToChat(conn, prompt) await startToChat(conn, prompt)
@@ -13,28 +13,37 @@ async def sendAudioMessage(conn, audios, text, text_index=0):
logger.bind(tag=TAG).info(f"发送第一段语音: {text}") logger.bind(tag=TAG).info(f"发送第一段语音: {text}")
await send_tts_message(conn, "sentence_start", text) await send_tts_message(conn, "sentence_start", text)
# 初始化流控参数 # 流控参数优化
frame_duration = 60 # 毫秒 original_frame_duration = 60 # 原始帧时长(毫秒
start_time = time.perf_counter() # 使用高精度计时器 adjusted_frame_duration = int(original_frame_duration * 0.8) # 缩短20%
play_position = 0 # 已播放的时长(毫秒) total_frames = len(audios) # 获取总帧数
compensation = total_frames * (original_frame_duration - adjusted_frame_duration) / 1000 # 补偿时间(秒)
start_time = time.perf_counter()
play_position = 0 # 已播放时长(毫秒)
for opus_packet in audios: for opus_packet in audios:
if conn.client_abort: if conn.client_abort:
return return
# 计算当前包的预期发送时间 # 计算带加速因子的预期时间
expected_time = start_time + (play_position / 1000) expected_time = start_time + (play_position / 1000)
current_time = time.perf_counter() current_time = time.perf_counter()
# 等待直到预期时间 # 流控等待(使用加速后的帧时长)
delay = expected_time - current_time delay = expected_time - current_time
if delay > 0: if delay > 0:
await asyncio.sleep(delay) await asyncio.sleep(delay)
# 发送音频包
await conn.websocket.send(opus_packet) await conn.websocket.send(opus_packet)
play_position += frame_duration # 更新播放位置 play_position += adjusted_frame_duration # 使用调整后的帧时长
# 补偿因加速损失的时长
if compensation > 0:
await asyncio.sleep(compensation)
await send_tts_message(conn, "sentence_end", text) await send_tts_message(conn, "sentence_end", text)
# 发送结束消息(如果是最后一个文本) # 发送结束消息(如果是最后一个文本)
if conn.llm_finish_task and text_index == conn.tts_last_text_index: if conn.llm_finish_task and text_index == conn.tts_last_text_index:
await send_tts_message(conn, 'stop', None) await send_tts_message(conn, 'stop', None)
@@ -5,6 +5,7 @@ from config.logger import setup_logging
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
class IntentProviderBase(ABC): class IntentProviderBase(ABC):
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
@@ -19,7 +20,7 @@ class IntentProviderBase(ABC):
logger.bind(tag=TAG).debug("Set LLM for intent provider") logger.bind(tag=TAG).debug("Set LLM for intent provider")
@abstractmethod @abstractmethod
async def detect_intent(self, dialogue_history: List[Dict]) -> str: async def detect_intent(self, dialogue_history: List[Dict], text: str) -> str:
""" """
检测用户最后一句话的意图 检测用户最后一句话的意图
Args: Args:
@@ -37,7 +37,7 @@ class IntentProvider(IntentProviderBase):
) )
return prompt return prompt
async def detect_intent(self, dialogue_history: List[Dict]) -> str: async def detect_intent(self, dialogue_history: List[Dict], text:str) -> str:
if not self.llm: if not self.llm:
raise ValueError("LLM provider not set") raise ValueError("LLM provider not set")
@@ -48,9 +48,8 @@ class IntentProvider(IntentProviderBase):
msgStr += f"User: {msg.content}\n" msgStr += f"User: {msg.content}\n"
elif msg.role== "assistant": elif msg.role== "assistant":
msgStr += f"Assistant: {msg.content}\n" msgStr += f"Assistant: {msg.content}\n"
msgStr += f"User: {text}\n"
user_prompt = f"请分析用户的意图:\n{msgStr}" user_prompt = f"请分析用户的意图:\n{msgStr}"
# 使用LLM进行意图识别 # 使用LLM进行意图识别
intent = self.llm.response_no_stream( intent = self.llm.response_no_stream(
system_prompt=self.promot, system_prompt=self.promot,
@@ -5,12 +5,14 @@ from config.logger import setup_logging
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
class IntentProvider(IntentProviderBase): class IntentProvider(IntentProviderBase):
async def detect_intent(self, dialogue_history: List[Dict]) -> str: async def detect_intent(self, dialogue_history: List[Dict], text: str) -> str:
""" """
默认的意图识别实现,始终返回继续聊天 默认的意图识别实现,始终返回继续聊天
Args: Args:
dialogue_history: 对话历史记录列表 dialogue_history: 对话历史记录列表
text: 本次对话记录
Returns: Returns:
固定返回"继续聊天" 固定返回"继续聊天"
""" """
@@ -51,30 +51,8 @@ class LLMProvider(LLMProviderBase):
tools=functions, tools=functions,
) )
current_function_call = None
current_content = ""
for chunk in stream: for chunk in stream:
delta = chunk.choices[0].delta yield chunk.choices[0].delta.content, chunk.choices[0].delta.tool_calls
if delta.content:
current_content += delta.content
yield {"type": "content", "content": delta.content}
if delta.tool_calls:
tool_call = delta.tool_calls[0]
# Handle the function call data using proper attribute access
if not current_function_call:
current_function_call = {
"function": {
"name": tool_call.function.name,
"arguments": tool_call.function.arguments
}
}
if current_function_call:
logger.bind(tag=TAG).debug(f"ollama Function call detected: {current_function_call}")
yield {"type": "function_call", "function_call": current_function_call}
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"Error in Ollama function call: {e}") logger.bind(tag=TAG).error(f"Error in Ollama function call: {e}")
@@ -53,30 +53,8 @@ class LLMProvider(LLMProviderBase):
tools=functions, tools=functions,
) )
current_function_call = None
current_content = ""
for chunk in stream: for chunk in stream:
delta = chunk.choices[0].delta yield chunk.choices[0].delta.content, chunk.choices[0].delta.tool_calls
if delta.content:
current_content += delta.content
yield {"type": "content", "content": delta.content}
if delta.tool_calls:
tool_call = delta.tool_calls[0]
# Handle the function call data using proper attribute access
if not current_function_call:
current_function_call = {
"function": {
"name": tool_call.function.name,
"arguments": tool_call.function.arguments
}
}
if current_function_call:
logger.bind(tag=TAG).debug(f"openai Function call detected: {current_function_call}")
yield {"type": "function_call", "function_call": current_function_call}
except Exception as e: except Exception as e:
self.logger.bind(tag=TAG).error(f"Error in function call streaming: {e}") self.logger.bind(tag=TAG).error(f"Error in function call streaming: {e}")
+9
View File
@@ -4,6 +4,7 @@ import yaml
import socket import socket
import subprocess import subprocess
import logging import logging
import re
def get_project_dir(): def get_project_dir():
@@ -119,3 +120,11 @@ def check_ffmpeg_installed():
error_msg += "1、按照项目的安装文档,正确进入conda环境\n" error_msg += "1、按照项目的安装文档,正确进入conda环境\n"
error_msg += "2、查阅安装文档,如何在conda环境中安装ffmpeg\n" error_msg += "2、查阅安装文档,如何在conda环境中安装ffmpeg\n"
raise ValueError(error_msg) raise ValueError(error_msg)
def extract_json_from_string(input_string):
"""提取字符串中的 JSON 部分"""
pattern = r'(\{.*\})'
match = re.search(pattern, input_string)
if match:
return match.group(1) # 返回提取的 JSON 字符串
return None