diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 146241ff..76f43373 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -462,16 +462,19 @@ class ConnectionHandler: function_id = None function_arguments = "" content_arguments = "" + for response in llm_responses: content, tools_call = response + if "content" in response: content = response["content"] tools_call = None if content is not None and len(content) > 0: - if len(response_message) <= 0 and ( - content == "```" or "" in content - ): - tool_call_flag = True + content_arguments += content + + if not tool_call_flag and content_arguments.startswith(""): + # print("content_arguments", content_arguments) + tool_call_flag = True if tools_call is not None: tool_call_flag = True @@ -483,9 +486,7 @@ class ConnectionHandler: function_arguments += tools_call[0].function.arguments if content is not None and len(content) > 0: - if tool_call_flag: - content_arguments += content - else: + if not tool_call_flag: response_message.append(content) if self.client_abort: @@ -546,8 +547,6 @@ class ConnectionHandler: self.logger.bind(tag=TAG).error( f"function call error: {content_arguments}" ) - else: - function_arguments = json.loads(function_arguments) if not bHasError: self.logger.bind(tag=TAG).info( f"function_name={function_name}, function_id={function_id}, function_arguments={function_arguments}" diff --git a/main/xiaozhi-server/core/providers/llm/base.py b/main/xiaozhi-server/core/providers/llm/base.py index d39a8589..97d0d8e7 100644 --- a/main/xiaozhi-server/core/providers/llm/base.py +++ b/main/xiaozhi-server/core/providers/llm/base.py @@ -35,4 +35,5 @@ class LLMProviderBase(ABC): """ # For providers that don't support functions, just return regular response for token in self.response(session_id, dialogue): - yield {"type": "content", "content": token} \ No newline at end of file + yield token, None + diff --git a/main/xiaozhi-server/core/providers/llm/dify/dify.py b/main/xiaozhi-server/core/providers/llm/dify/dify.py index 030aba05..93114129 100644 --- a/main/xiaozhi-server/core/providers/llm/dify/dify.py +++ b/main/xiaozhi-server/core/providers/llm/dify/dify.py @@ -2,6 +2,7 @@ import json from config.logger import setup_logging import requests from core.providers.llm.base import LLMProviderBase +from core.providers.llm.system_prompt import get_system_prompt_for_function TAG = __name__ logger = setup_logging() @@ -81,3 +82,23 @@ class LLMProvider(LLMProviderBase): except Exception as e: logger.bind(tag=TAG).error(f"Error in response generation: {e}") yield "【服务响应异常】" + + def response_with_functions(self, session_id, dialogue, functions=None): + if len(dialogue) == 2 and functions is not None and len(functions) > 0: + # 第一次调用llm, 取最后一条用户消息,附加tool提示词 + last_msg = dialogue[-1]["content"] + function_str = json.dumps(functions, ensure_ascii=False) + modify_msg = get_system_prompt_for_function(function_str) + last_msg + dialogue[-1]["content"] = modify_msg + + # 如果最后一个是 role="tool",附加到user上 + if len(dialogue) > 1 and dialogue[-1]["role"] == "tool": + assistant_msg = "tool call result: " + dialogue[-1]["content"] + while len(dialogue) > 1 : + if dialogue[-1]["role"] == "user": + dialogue[-1]["content"] += assistant_msg + break + dialogue.pop() + + for token in self.response(session_id, dialogue): + yield token, None \ No newline at end of file diff --git a/main/xiaozhi-server/core/providers/llm/ollama/ollama.py b/main/xiaozhi-server/core/providers/llm/ollama/ollama.py index 179a85b1..fb34ced5 100644 --- a/main/xiaozhi-server/core/providers/llm/ollama/ollama.py +++ b/main/xiaozhi-server/core/providers/llm/ollama/ollama.py @@ -63,4 +63,4 @@ class LLMProvider(LLMProviderBase): except Exception as e: logger.bind(tag=TAG).error(f"Error in Ollama function call: {e}") - yield {"type": "content", "content": f"【Ollama服务响应异常: {str(e)}】"} + yield f"【Ollama服务响应异常: {str(e)}】", None diff --git a/main/xiaozhi-server/core/providers/llm/openai/openai.py b/main/xiaozhi-server/core/providers/llm/openai/openai.py index d6e66dcf..2c04662b 100644 --- a/main/xiaozhi-server/core/providers/llm/openai/openai.py +++ b/main/xiaozhi-server/core/providers/llm/openai/openai.py @@ -74,4 +74,4 @@ class LLMProvider(LLMProviderBase): except Exception as e: logger.bind(tag=TAG).error(f"Error in function call streaming: {e}") - yield {"type": "content", "content": f"【OpenAI服务响应异常: {e}】"} + yield f"【OpenAI服务响应异常: {e}】", None diff --git a/main/xiaozhi-server/core/providers/llm/system_prompt.py b/main/xiaozhi-server/core/providers/llm/system_prompt.py new file mode 100644 index 00000000..dc43b901 --- /dev/null +++ b/main/xiaozhi-server/core/providers/llm/system_prompt.py @@ -0,0 +1,98 @@ +def get_system_prompt_for_function(functions: str) -> str: + """ + 生成系统提示信息 + :param functions: 可用的函数列表 + :return: 系统提示信息 + """ + + SYSTEM_PROMPT = f""" +==== + +TOOL USE + +You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. + +# Tool Use Formatting + +Tool use is formatted using JSON-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure: + + +{{ + "name": "function name", + "arguments": {{ + "param1": "value1", + "param2": "value2", + // Add more parameters as needed, if parameters are required, you must provide them + }} +}} + + +For example: +if you got tool as follow + +{{ + "type": "function", + "function": {{ + "name": "handle_exit_intent", + "description": "当用户想结束对话或需要退出系统时调用", + "parameters": {{ + "type": "object", + "properties": {{ + "say_goodbye": {{ + "type": "string", + "description": "和用户友好结束对话的告别语", + }} + }}, + "required": ["say_goodbye"], + }}, + }}, +}} + +you should respond with the following format: + + +{{ + "name": "handle_exit_intent", + "arguments": {{ + "say_goodbye": "再见,祝您生活愉快!" + }} +}} + + + +Always adhere to this format for the tool use to ensure proper parsing and execution. + +# Tools + +{functions} + +# Tool Use Guidelines + +1. In tags, assess what information you already have and what information you need to proceed with the task. +2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like \`ls\` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. +3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. +4. Formulate your tool use using the XML format specified for each tool. +5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +- Information about whether the tool succeeded or failed, along with any reasons for failure. +- Linter errors that may have arisen due to the changes you made, which you'll need to address. +- New terminal output in reaction to the changes, which you may need to consider or act upon. +- Any other relevant feedback or information related to the tool use. +6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. + +It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: +1. Confirm the success of each step before proceeding. +2. Address any issues or errors that arise immediately. +3. Adapt your approach based on new information or unexpected results. +4. Ensure that each action builds correctly on the previous ones. + +By waiting for and carefully considering the user's response after each tool use, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work. + +==== + +USER'S CUSTOM INSTRUCTIONS + +The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines. + + """ + + return SYSTEM_PROMPT \ No newline at end of file diff --git a/main/xiaozhi-server/core/utils/util.py b/main/xiaozhi-server/core/utils/util.py index dd56f5ae..cff226bf 100644 --- a/main/xiaozhi-server/core/utils/util.py +++ b/main/xiaozhi-server/core/utils/util.py @@ -193,7 +193,7 @@ def check_ffmpeg_installed(): def extract_json_from_string(input_string): """提取字符串中的 JSON 部分""" pattern = r"(\{.*\})" - match = re.search(pattern, input_string) + match = re.search(pattern, input_string, re.DOTALL) #添加 re.DOTALL if match: return match.group(1) # 返回提取的 JSON 字符串 return None