如何通过将工具调用注入到 system_prompt 来让 deepseekr1 也支持工具调用

190 阅读4分钟

image.png

项目 github 地址

关键在于 FunctionCallingPromptTemplate 类以及它与 LocalModel 类中 _prepare_chat_messages 方法的协作。

  1. 定义工具的格式化模板: 你的代码中有两个关键的字符串常量:

    • DEFAULT_FUNCTION_CALL_PROMPT_TEMPLATE: 这是一个通用的模板,它会告诉模型,你有一些工具可以使用,并定义了当模型决定调用工具时,它应该以什么样的 JSON 格式来响应(一个包含 tool_calls 数组的 JSON 对象)。这种格式与 OpenAI 的函数调用响应非常相似。
    • DEEPSEEK_FUNCTION_CALL_PROMPT_TEMPLATE: 这是专门为 DeepSeek 模型设计的模板。DeepSeek 模型(特别是早期版本或某些变体,例如 DeepSeek-R1)可能不理解标准 JSON,但更倾向于使用 XML 标签(如 <tool_code><available_tools>)来表示工具调用。这个模板指导模型使用这些 XML 标签来包裹其工具调用意图。
  2. FunctionCallingPromptTemplate 的作用:

    • 这个类负责根据你传入的 tool_format"default""deepseek")选择正确的模板。
    • 它的 format 方法接收一个 tools 列表(这些工具函数会被转换成 JSON Schema 格式),然后将这些工具的定义**序列化(json.dumps)**并嵌入到模板字符串的 {tools_definition} 占位符中。
    • 对于 DeepSeek 格式,它会将 JSON 化的工具定义进一步包裹在 <available_tools>\n...\n</available_tools> 这样的 XML 标签中,以符合 DeepSeek 模型预期的输入格式。
    • 最终,format 方法会返回一个完整的、包含了工具定义的提示字符串。
  3. LocalModel_prepare_chat_messages 的注入逻辑: 这是真正执行注入的地方:

    • 当你在 LocalModel 实例上调用 chatchat_sync 方法时,它会首先调用私有方法 _prepare_chat_messages(messages)
    • 在这个方法内部,有一个关键的判断:
      if not self._config.supports_native_tools and self.tools:
          # ... 构造 tool_injection_prompt ...
      
      这意味着:如果模型不支持原生工具调用 (self._config.supports_native_toolsFalse) 并且你已经通过 bind_tools 方法绑定了工具 (self.tools 不为空),那么就会触发提示注入。
    • 它会根据 self._config.modelname 是否以 "deepseek" 开头来选择使用 DEEPSEEK_FUNCTION_CALL_PROMPT_TEMPLATE 还是 DEFAULT_FUNCTION_CALL_PROMPT_TEMPLATE,并设置相应的 tool_format_type
    • 然后,它会调用 FunctionCallingPromptTemplateformat 方法,生成一个包含所有工具定义和使用说明的字符串,即 tool_injection_prompt
    • 最重要的一步: 它会遍历现有的所有消息 (all_messages)。
      • 如果找到一个角色为 "system" 的消息,它会把 tool_injection_prompt 字符串**前置(prepend)**到这个现有系统消息的 content 中,中间用换行符分隔。
      • 如果消息列表中没有 system 消息,它会在列表的最前面插入一个新的 system 消息,其 content 就是 tool_injection_prompt

总结工作流程

  1. 初始化 LocalModel: 你会创建一个 LocalModel 实例,并提供 LocalModelConfig,其中 supports_native_tools 应设置为 False (对于 DeepSeek-R1 或其他不支持原生工具的模型)。
  2. 绑定工具: 使用 model.bind_tools([get_weather, get_directions]) 将你的工具函数(或其他格式的工具定义)绑定到模型实例上。
  3. 发起聊天请求: 当你调用 model.chat_sync(messages)await model.chat(messages) 时:
    • _prepare_chat_messages 会被调用。
    • 由于 supports_native_toolsFalse 且有绑定工具,它会根据模型名称(是否 DeepSeek)选择合适的提示模板。
    • FunctionCallingPromptTemplate 会把你的工具函数转换为 JSON Schema 格式,然后根据选定的模板(例如,为 DeepSeek 模型包裹在 <available_tools> 标签中),生成一个包含所有工具定义和指令的文本字符串。
    • 这个文本字符串随后被注入到整个对话的第一个 system 消息的开头。
    • 最后,包含这个增强型 system 消息的完整消息列表会被发送到 Ollama 的 /api/chat 端点。

通过这种方式,即使模型本身不具备原生的工具调用能力,它也能通过理解 system 消息中精心构造的指令和工具定义,从而在响应中生成符合预期格式的工具调用(例如,DeepSeek 可能会输出 <tool_code>...</tool_code>,而其他模型可能输出 { "tool_calls": [...] })。然后,你的应用程序就可以解析这些响应,并执行相应的工具功能。