| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- import json
- import uuid
- import asyncio
- from typing import TYPE_CHECKING
- if TYPE_CHECKING:
- from core.connection import ConnectionHandler
- from core.utils.dialogue import Message
- from core.providers.tts.dto.dto import ContentType
- from core.handle.helloHandle import checkWakeupWords
- from plugins_func.register import Action, ActionResponse
- from core.handle.sendAudioHandle import send_stt_message
- from core.handle.reportHandle import enqueue_tool_report
- from core.utils.util import remove_punctuation_and_length
- from core.providers.tts.dto.dto import TTSMessageDTO, SentenceType
- TAG = __name__
- STRICT_HAZARD_MARK_PHRASES = {
- "标记急转弯": "sharp_turn",
- "标记起转弯": "sharp_turn",
- "标记急弯": "sharp_turn",
- "标记左转弯": "left_turn",
- "标记右转弯": "right_turn",
- "标记障碍": "obstacle",
- "标记颠簸": "bump",
- "标记大坑": "bump",
- "标记坑": "bump",
- "标记跳台": "jump",
- "标记陡坡": "slope",
- "标记涉水": "water",
- "标记水坑": "water",
- }
- NAV_ALERT_ECHO_PHRASES = {
- "前方左转弯",
- "前方右转弯",
- "准备急转弯",
- "前方颠簸",
- "前方水坑",
- "前方障碍",
- "前方跳台",
- "前方陡坡",
- "前方风险点",
- "注意左转弯",
- "注意右转弯",
- "注意急转弯",
- "注意颠簸",
- "注意水坑",
- "注意障碍",
- }
- async def handle_user_intent(conn: "ConnectionHandler", text):
- # 预处理输入文本,处理可能的JSON格式
- try:
- if text.strip().startswith("{") and text.strip().endswith("}"):
- parsed_data = json.loads(text)
- if isinstance(parsed_data, dict) and "content" in parsed_data:
- text = parsed_data["content"] # 提取content用于意图分析
- conn.current_speaker = parsed_data.get("speaker") # 保留说话人信息
- except (json.JSONDecodeError, TypeError):
- pass
- # 检查是否有明确的退出命令
- _, filtered_text = remove_punctuation_and_length(text)
- if await check_direct_exit(conn, filtered_text):
- return True
- # 检查是否是唤醒词
- if await checkWakeupWords(conn, filtered_text):
- return True
- if should_ignore_nav_alert_echo(filtered_text):
- conn.logger.bind(tag=TAG).info(f"忽略导航播报回采文本: {filtered_text}")
- return True
- direct_function_call = match_direct_nav_command(filtered_text)
- if direct_function_call is not None:
- function_name, function_args = direct_function_call
- conn.logger.bind(tag=TAG).info(
- f"识别到直达导航命令: {filtered_text} -> {function_name}"
- )
- return await dispatch_function_call(
- conn,
- text,
- function_name,
- function_args,
- )
- if conn.intent_type == "function_call":
- # 使用支持function calling的聊天方法,不再进行意图分析
- return False
- # 使用LLM进行意图分析
- intent_result = await analyze_intent_with_llm(conn, text)
- if not intent_result:
- return False
- # 会话开始时生成sentence_id
- conn.sentence_id = str(uuid.uuid4().hex)
- # 处理各种意图
- return await process_intent_result(conn, intent_result, text)
- async def check_direct_exit(conn: "ConnectionHandler", text):
- """检查是否有明确的退出命令"""
- _, text = remove_punctuation_and_length(text)
- cmd_exit = conn.cmd_exit
- for cmd in cmd_exit:
- if text == cmd:
- conn.logger.bind(tag=TAG).info(f"识别到明确的退出命令: {text}")
- await send_stt_message(conn, text)
- await conn.close()
- return True
- return False
- async def analyze_intent_with_llm(conn: "ConnectionHandler", text):
- """使用LLM分析用户意图"""
- if not hasattr(conn, "intent") or not conn.intent:
- conn.logger.bind(tag=TAG).warning("意图识别服务未初始化")
- return None
- # 对话历史记录
- dialogue = conn.dialogue
- try:
- intent_result = await conn.intent.detect_intent(conn, dialogue.dialogue, text)
- return intent_result
- except Exception as e:
- conn.logger.bind(tag=TAG).error(f"意图识别失败: {str(e)}")
- return None
- def match_direct_nav_command(filtered_text: str):
- text = (filtered_text or "").strip()
- if not text:
- return None
- if text in {
- "开始录制路线",
- "开始记录路线",
- "录制路线",
- "开始记录",
- "开始记路线",
- }:
- return "nav_start_record", {}
- if text in {
- "结束录制",
- "停止录制",
- "结束记录",
- "停止记录",
- "录制结束",
- }:
- return "nav_stop_record", {}
- if text in {
- "开始预警",
- "开始播报",
- "开始领航",
- "开始导航",
- }:
- return "nav_start_run", {}
- if text in {
- "停止预警",
- "结束预警",
- "停止播报",
- "关闭预警",
- }:
- return "nav_stop_run", {}
- if text.startswith("标记"):
- if text in STRICT_HAZARD_MARK_PHRASES:
- return "nav_mark_hazard", {"hazard_type": STRICT_HAZARD_MARK_PHRASES[text]}
- return "nav_mark_hazard_reject", {}
- return None
- def should_ignore_nav_alert_echo(filtered_text: str) -> bool:
- text = (filtered_text or "").strip()
- if not text:
- return False
- return text in NAV_ALERT_ECHO_PHRASES
- async def dispatch_function_call(
- conn: "ConnectionHandler",
- original_text: str,
- function_name: str,
- function_args: dict | None = None,
- ):
- conn.sentence_id = str(uuid.uuid4().hex)
- function_args = function_args or {}
- function_args_json = json.dumps(function_args, ensure_ascii=False)
- function_call_data = {
- "name": function_name,
- "id": str(uuid.uuid4().hex),
- "arguments": function_args_json,
- }
- await send_stt_message(conn, original_text)
- conn.client_abort = False
- enqueue_tool_report(conn, function_name, function_args)
- def process_function_call():
- conn.dialogue.put(Message(role="user", content=original_text))
- tool_call_timeout = int(conn.config.get("tool_call_timeout", 30))
- try:
- result = asyncio.run_coroutine_threadsafe(
- conn.func_handler.handle_llm_function_call(conn, function_call_data),
- conn.loop,
- ).result(timeout=tool_call_timeout)
- except Exception as e:
- conn.logger.bind(tag=TAG).error(f"工具调用失败: {e}")
- result = ActionResponse(
- action=Action.ERROR,
- result="工具调用超时,请一会再试下哈",
- response="工具调用超时,请一会再试下哈",
- )
- if result:
- enqueue_tool_report(
- conn,
- function_name,
- function_args,
- str(result.result) if result.result else None,
- report_tool_call=False,
- )
- if result.action == Action.RESPONSE:
- text = result.response
- if text is not None:
- speak_txt(conn, text)
- elif result.action == Action.REQLLM:
- text = result.result
- conn.dialogue.put(Message(role="tool", content=text))
- llm_result = conn.intent.replyResult(text, original_text)
- if llm_result is None:
- llm_result = text
- speak_txt(conn, llm_result)
- elif result.action == Action.NOTFOUND or result.action == Action.ERROR:
- text = result.response if result.response else result.result
- if text is not None:
- speak_txt(conn, text)
- elif function_name != "play_music":
- text = result.response
- if text is None:
- text = result.result
- if text is not None:
- speak_txt(conn, text)
- conn.executor.submit(process_function_call)
- return True
- async def process_intent_result(
- conn: "ConnectionHandler", intent_result, original_text
- ):
- """处理意图识别结果"""
- try:
- # 尝试将结果解析为JSON
- intent_data = json.loads(intent_result)
- # 检查是否有function_call
- if "function_call" in intent_data:
- # 直接从意图识别获取了function_call
- conn.logger.bind(tag=TAG).debug(
- f"检测到function_call格式的意图结果: {intent_data['function_call']['name']}"
- )
- function_name = intent_data["function_call"]["name"]
- if function_name == "continue_chat":
- return False
- if function_name == "result_for_context":
- await send_stt_message(conn, original_text)
- conn.client_abort = False
- def process_context_result():
- conn.dialogue.put(Message(role="user", content=original_text))
- from core.utils.current_time import get_current_time_info
- current_time, today_date, today_weekday, lunar_date = (
- get_current_time_info()
- )
- # 构建带上下文的基础提示
- context_prompt = f"""当前时间:{current_time}
- 今天日期:{today_date} ({today_weekday})
- 今天农历:{lunar_date}
- 请根据以上信息回答用户的问题:{original_text}"""
- response = conn.intent.replyResult(context_prompt, original_text)
- speak_txt(conn, response)
- conn.executor.submit(process_context_result)
- return True
- function_args = {}
- if "arguments" in intent_data["function_call"]:
- function_args = intent_data["function_call"]["arguments"]
- if function_args is None:
- function_args = {}
- if isinstance(function_args, str):
- function_args = json.loads(function_args) if function_args else {}
- return await dispatch_function_call(
- conn,
- original_text,
- function_name,
- function_args if isinstance(function_args, dict) else {},
- )
- return False
- except json.JSONDecodeError as e:
- conn.logger.bind(tag=TAG).error(f"处理意图结果时出错: {e}")
- return False
- def speak_txt(conn: "ConnectionHandler", text):
- # 记录文本到 sentence_id 映射
- conn.tts.store_tts_text(conn.sentence_id, text)
- conn.tts.tts_text_queue.put(
- TTSMessageDTO(
- sentence_id=conn.sentence_id,
- sentence_type=SentenceType.FIRST,
- content_type=ContentType.ACTION,
- )
- )
- conn.tts.tts_one_sentence(conn, ContentType.TEXT, content_detail=text)
- conn.tts.tts_text_queue.put(
- TTSMessageDTO(
- sentence_id=conn.sentence_id,
- sentence_type=SentenceType.LAST,
- content_type=ContentType.ACTION,
- )
- )
- conn.dialogue.put(Message(role="assistant", content=text))
|