关键在于 FunctionCallingPromptTemplate 类以及它与 LocalModel 类中 _prepare_chat_messages 方法的协作。
-
定义工具的格式化模板: 你的代码中有两个关键的字符串常量:
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 标签来包裹其工具调用意图。
-
FunctionCallingPromptTemplate的作用:- 这个类负责根据你传入的
tool_format("default"或"deepseek")选择正确的模板。 - 它的
format方法接收一个tools列表(这些工具函数会被转换成 JSON Schema 格式),然后将这些工具的定义**序列化(json.dumps)**并嵌入到模板字符串的{tools_definition}占位符中。 - 对于 DeepSeek 格式,它会将 JSON 化的工具定义进一步包裹在
<available_tools>\n...\n</available_tools>这样的 XML 标签中,以符合 DeepSeek 模型预期的输入格式。 - 最终,
format方法会返回一个完整的、包含了工具定义的提示字符串。
- 这个类负责根据你传入的
-
LocalModel中_prepare_chat_messages的注入逻辑: 这是真正执行注入的地方:- 当你在
LocalModel实例上调用chat或chat_sync方法时,它会首先调用私有方法_prepare_chat_messages(messages)。 - 在这个方法内部,有一个关键的判断:
这意味着:如果模型不支持原生工具调用 (if not self._config.supports_native_tools and self.tools: # ... 构造 tool_injection_prompt ...self._config.supports_native_tools为False) 并且你已经通过bind_tools方法绑定了工具 (self.tools不为空),那么就会触发提示注入。 - 它会根据
self._config.modelname是否以 "deepseek" 开头来选择使用DEEPSEEK_FUNCTION_CALL_PROMPT_TEMPLATE还是DEFAULT_FUNCTION_CALL_PROMPT_TEMPLATE,并设置相应的tool_format_type。 - 然后,它会调用
FunctionCallingPromptTemplate的format方法,生成一个包含所有工具定义和使用说明的字符串,即tool_injection_prompt。 - 最重要的一步: 它会遍历现有的所有消息 (
all_messages)。- 如果找到一个角色为
"system"的消息,它会把tool_injection_prompt字符串**前置(prepend)**到这个现有系统消息的content中,中间用换行符分隔。 - 如果消息列表中没有
system消息,它会在列表的最前面插入一个新的system消息,其content就是tool_injection_prompt。
- 如果找到一个角色为
- 当你在
总结工作流程
- 初始化
LocalModel: 你会创建一个LocalModel实例,并提供LocalModelConfig,其中supports_native_tools应设置为False(对于 DeepSeek-R1 或其他不支持原生工具的模型)。 - 绑定工具: 使用
model.bind_tools([get_weather, get_directions])将你的工具函数(或其他格式的工具定义)绑定到模型实例上。 - 发起聊天请求: 当你调用
model.chat_sync(messages)或await model.chat(messages)时:_prepare_chat_messages会被调用。- 由于
supports_native_tools是False且有绑定工具,它会根据模型名称(是否 DeepSeek)选择合适的提示模板。 FunctionCallingPromptTemplate会把你的工具函数转换为 JSON Schema 格式,然后根据选定的模板(例如,为 DeepSeek 模型包裹在<available_tools>标签中),生成一个包含所有工具定义和指令的文本字符串。- 这个文本字符串随后被注入到整个对话的第一个
system消息的开头。 - 最后,包含这个增强型
system消息的完整消息列表会被发送到 Ollama 的/api/chat端点。
通过这种方式,即使模型本身不具备原生的工具调用能力,它也能通过理解 system 消息中精心构造的指令和工具定义,从而在响应中生成符合预期格式的工具调用(例如,DeepSeek 可能会输出 <tool_code>...</tool_code>,而其他模型可能输出 { "tool_calls": [...] })。然后,你的应用程序就可以解析这些响应,并执行相应的工具功能。