LLamaWorker 是一个基于 LLamaSharp 项目开发的可以在本地运行大模型服务,并提供与 OpenAI / Azure OpenAI 兼容的 API。同时,通过工具提示词的配置,提供函数调用 Function Call 能力,为开发者提供更多的可能。
1. 背景
在人工智能领域,大型语言模型(LLM)正在以其强大的自然语言处理能力改变游戏规则。为了满足开发者将这些模型集成到自己应用程序中的需求,我开发了 LLamaWorker。
LLamaWorker 是一个基于 LLamaSharp 项目开发的可以在本地运行大模型服务,并提供与 OpenAI / Azure OpenAI 兼容的 API。除了兼容性,LLamaWorker 还提供了一些其他功能,例如多模型支持、流式响应、嵌入支持、对话模版等。同时,对于支持函数调用的模型,通过工具提示词的配置,就可以使用函数调用 Function Call 能力。
LLamaWorker 项目地址:https://github.com/sangyuxiaowu/LLamaWorker?wt.mc_id=DT-MVP-5005195
2. 函数调用功能
经过这段时间的开发,LLamaWorker 迎来了重大更新。这次更新不仅优化了现有功能,还新增了许多实用特性,尤其是函数调用功能。它允许开发者在模型生成的对话中插入函数调用,从而实现更复杂的任务处理。以下是函数调用的配置示例:
"ToolPromptConfig": [
{
"PromptConfigDesc": "千问模板",
"FN_NAME": "✿FUNCTION✿",
"FN_ARGS": "✿ARGS✿",
"FN_RESULT": "✿RESULT✿",
"FN_EXIT": "✿RETURN✿",
"FN_STOP_WORDS": [ "✿RESULT✿", "✿RETURN✿" ],
"FN_CALL_TEMPLATE_INFO": {
"zh": "# 工具\n\n## 你拥有如下工具:\n\n{tool_descs}",
"en": "# Tools\n\n## You have access to the following tools:\n\n{tool_descs}"
},
"FN_CALL_TEMPLATE_FMT": {
"zh": "## 你可以在回复中插入零次、一次或多次以下命令以调用工具:\n\n{0}: 工具名称,必须是[{4}]之一。\n{1}: 工具输入\n{2}: 工具结果\n{3}: 根据工具结果进行回复,需将图片用![](url)渲染出来",
"en": "## When you need to call a tool, please insert the following command in your reply, which can be called zero or multiple times according to your needs:\n\n{0}: The tool to use, should be one of [{4}]\n{1}: The input of the tool\n{2}: Tool results\n{3}: Reply based on tool results. Images need to be rendered as ![](url)"
},
"FN_CALL_TEMPLATE_FMT_PARA": {
"zh": "## 你可以在回复中插入以下命令以并行调用N个工具:\n\n{0}: 工具1的名称,必须是[{4}]之一\n{1}: 工具1的输入\n{0}: 工具2的名称\n{1}: 工具2的输入\n...\n{0}: 工具N的名称\n{1}: 工具N的输入\n{2}: 工具1的结果\n{2}: 工具2的结果\n...\n{2}: 工具N的结果\n{3}: 根据工具结果进行回复,需将图片用![](url)渲染出来",
"en": "## Insert the following command in your reply when you need to call N tools in parallel:\n\n{0}: The name of tool 1, should be one of [{4}]\n{1}: The input of tool 1\n{0}: The name of tool 2\n{1}: The input of tool 2\n...\n{0}: The name of tool N\n{1}: The input of tool N\n{2}: The result of tool 1\n{2}: The result of tool 2\n...\n{2}: The result of tool N\n{3}: Reply based on tool results. Images need to be rendered as ![](url)"
},
"ToolDescTemplate": {
"zh": "### {0}\n\n{1}: {2} 输入参数:{3}",
"en": "### {0}\n\n{1}: {2} Parameters: {3}"
}
}
]
在这个配置中,我们使用了 Qwen2
模型提供的工具配置模板,通过配置 ToolPromptConfig
,我们可以定义工具的调用方式、参数、结果等信息。这样一来,我们就可以在对话中插入函数调用,实现更多的功能。
该模板已经测试通过,当然你也可以进行调优使用。后续会提供并测试 Llama 3.1
的函数调用模板。
对于工具提示词的生成,我们新增了 ToolPromptGenerator
类,用于处理工具提示词,核心代码如下:
/// <summary>
/// 生成工具提示词
/// </summary>
/// <param name="req">原始对话生成请求</param>
/// <param name="tpl">模版序号</param>
/// <param name="lang">语言</param>
/// <returns></returns>
public string GenerateToolPrompt(ChatCompletionRequest req, int tpl = 0, string lang = "zh")
{
// 如果没有工具或者工具选择为 none,则返回空字符串
if (req.tools == null || req.tools.Length == 0 || (req.tool_choice != null && req.tool_choice.ToString() == "none"))
{
return string.Empty;
}
var config = _config[tpl];
var toolDescriptions = req.tools.Select(tool => GetFunctionDescription(tool.function, config.ToolDescTemplate[lang])).ToArray();
var toolNames = string.Join(",", req.tools.Select(tool => tool.function.name));
var toolDescTemplate = config.FN_CALL_TEMPLATE_INFO[lang];
var toolDesc = string.Join("\n\n", toolDescriptions);
var toolSystem = toolDescTemplate.Replace("{tool_descs}", toolDesc);
var parallelFunctionCalls = req.tool_choice?.ToString() == "parallel";
var toolTemplate = parallelFunctionCalls ? config.FN_CALL_TEMPLATE_FMT_PARA[lang] : config.FN_CALL_TEMPLATE_FMT[lang];
var toolPrompt = string.Format(toolTemplate, config.FN_NAME, config.FN_ARGS, config.FN_RESULT, config.FN_EXIT, toolNames);
return $"\n\n{toolSystem}\n\n{toolPrompt}";
}
private string GetFunctionDescription(FunctionInfo function, string toolDescTemplate)
{
var nameForHuman = function.name;
var nameForModel = function.name;
var descriptionForModel = function.description ?? string.Empty;
var parameters = JsonSerializer.Serialize(function.parameters, new JsonSerializerOptions { Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) });
return string.Format(toolDescTemplate, nameForHuman, nameForModel, descriptionForModel, parameters).Trim();
}
其实函数调用并没有那么神秘,也是提示词的艺术。在这个函数中,我们首先判断是否有工具,如果没有工具或者工具选择为 none,则返回空字符串。然后,我们根据配置生成工具提示词,包括工具描述、工具名称等信息。最后,我们根据是否并行调用生成工具提示词。
这个函数的实现非常简单,但是却非常实用。通过这个函数,我们可以在对话中插入函数调用,实现更多的功能。下面,我们来看一下如何使用函数调用功能。
3. 使用函数调用
在项目中,新增了函数调用的请求示例,在运行项目后,可以通过打开 LLamaWorker.http
文件,查看函数调用的请求示例。
###
# 测试函数调用
POST {{LLamaWorker_HostAddress}}/v1/chat/completions
Content-Type: application/json
{
"model": "gpt-3.5-turbo",
"messages": [
{
"role": "system",
"content": "你是用户 SangYuXiaoWu 的助理,你会遵守规则。您将完成所需的步骤并在采取任何后续行动之前请求批准。\r\n如果用户不提供足够的信息让你完成一项任务,你会一直问问题,直到你有足够的信息来完成任务。"
},
{
"role": "user",
"content": "帮我给我的老板,老周写一封邮件说我要涨薪"
}
],
"tools": [
{
"function": {
"name": "AuthorEmailPlanner-GenerateRequiredSteps",
"description": "返回编写电子邮件所需的必要步骤。",
"parameters": {
"type": "object",
"required": [
"topic",
"recipients"
],
"properties": {
"topic": {
"type": "string",
"description": "电子邮件内容的2-3句简要描述"
},
"recipients": {
"type": "string",
"description": "收件人的描述"
}
}
}
},
"type": "function"
},
{
"function": {
"name": "EmailPlugin-SendEmail",
"description": "向收件人发送电子邮件。",
"parameters": {
"type": "object",
"required": [
"recipientEmails",
"subject",
"body"
],
"properties": {
"recipientEmails": {
"type": "string",
"description": "以分号分隔的收件人电子邮件列表"
},
"subject": {
"type": "string"
},
"body": {
"type": "string"
}
}
}
},
"type": "function"
}
],
"tool_choice": "auto"
}
整个示例的完整流程提供了四个相关请求示例,可以通过这个示例来了解如何使用函数调用功能。
4. 结语
对于函数调用,当前只是初步实现了非流式的函数调用功能,后续计划将继续优化函数调用功能,包括流式函数调用、函数调用的并行执行等。同时,我也会继续完善 LLamaWorker 的其他功能。欢迎大家关注 LLamaWorker 项目,一起探索更多的可能性!
LLamaWorker 项目的目标是为开发者社区提供一个高性能、易于使用的工具,以便更好地利用大型语言模型的能力。无论您是在构建聊天机器人、内容生成工具还是任何需要自然语言处理能力的应用,LLamaWorker 都能为您提供强大的支持。
我非常期待看到社区成员如何使用 LLamaWorker 来实现他们的创意和项目。如果您对 LLamaWorker 有任何反馈或建议,欢迎通过 GitHub Issues 或 Pull Requests 与我交流。让我们一起推动开源社区的发展,解锁更多的可能性!