通义千问 function call 提示词模板
提示词出处: Qwen-Agent
提示词如下:
- 英文
Tools
## You have access to the following tools:
{tool_descs}
## 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:
✿FUNCTION✿: The tool to use, should be one of [{tool_names}]
✿ARGS✿: The input of the tool
✿RESULT✿: The result returned by the tool. The image needs to be rendered as 
✿RETURN✿: Reply based on tool result
- 中文
# 工具
## 你拥有如下工具:
{tool_descs}
## 你可以在回复中插入零次、一次或多次以下命令以调用工具:
✿FUNCTION✿: 工具名称,必须是[{tool_names}]之一。
✿ARGS✿: 工具输入
✿RESULT✿: 工具结果,需将图片用渲染出来。
✿RETURN✿: 根据工具结果进行回复
这里我用 golang 写一个示例,实测表现稳定,支持本地 qwen模型
`(ollama 的API 需要指定好终止词 ✿RESULT✿)`
温馨提示: 演示代码 实现的是一个终端工具,测试请慎重,以免AI给你删除了不该删的文件😄
package main
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"time"
)
// QWEN_API_KEY 通义千问的 API Key
// (这里获取 https://help.aliyun.com/document_detail/2579563.html
const QWEN_API_KEY = "sk-yourapi-key"
/* 先封装一个Agent Tool 的 interface */
type ToolArgs struct {
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"`
}
// Tool 封装一个 Agent Tool 的 interface
type Tool interface {
// GetArgs 获取工具参数
GetArgs() []ToolArgs
// GetName 获取工具名称
GetName() string
// GetDescription 获取工具描述
GetDescription() string
// Invoke 调用工具
Invoke(args string) (string, error)
}
/* 这里实现一个可以调用终端的工具 */
type BashTool struct {
}
type BashToolArgs struct {
Cmd string `json:"cmd"`
}
func (b BashTool) GetArgs() []ToolArgs {
return []ToolArgs{
{
Name: "cmd",
Description: "The command to run",
Type: "string",
},
}
}
func (b BashTool) GetName() string {
return "bash"
}
func (b BashTool) GetDescription() string {
return "A bash tool that can run any bash command"
}
func (b BashTool) Invoke(args string) (string, error) {
param := BashToolArgs{}
err := json.Unmarshal([]byte(args), ¶m)
if err != nil {
return "", err
}
command := exec.Command("bash", "-c", param.Cmd)
// 捕获命令的输出
output, err := command.CombinedOutput()
if err != nil {
// 如果有错误,打印错误信息
fmt.Printf("Error: %s\n", err)
return "", err
}
return string(output), nil
}
/* 这里是提示词生成与解析部分代码 */
const fnTag = "✿FUNCTION✿"
const argsTag = "✿ARGS✿"
const resultTag = "✿RESULT✿"
const returnTag = "✿RETURN✿"
var __prompt = `
# 运维专员
你是一个运维专员,你的任务是帮助用户解决各种运维问题。你可以使用如下工具:
%s
## 你可以在回复中插入零次、一次或多次以下命令以调用工具:
%s: 工具名称,必须是[%s]之一。
%s: 工具输入
%s: 工具结果,需将图片用渲染出来。
%s: 根据工具结果进行回复
`
// 声明一个全局变量来保存工具列表
var globalTools = []Tool{
BashTool{},
}
// 构造工具的提示词
func getToolPrompt() string {
var str = ""
for _, tool := range globalTools {
args := tool.GetArgs()
argsStr, _ := json.Marshal(args)
str += fmt.Sprintf("%s: %s 输入参数: %s \n", tool.GetName(), tool.GetDescription(), string(argsStr))
}
return str
}
func getToolNames() []string {
var arr = []string{}
for _, tool := range globalTools {
arr = append(arr, tool.GetName())
}
return arr
}
// ExecFn 执行工具
func ExecFn(fnName string, args string) string {
for _, tool := range globalTools {
if tool.GetName() == fnName {
res, err := tool.Invoke(args)
if err != nil {
fmt.Printf("Error: %v", err)
res = fmt.Sprintf("%v", err)
}
return fmt.Sprintf("%s:%s", resultTag, res)
}
}
return fmt.Sprintf("%s:%s", resultTag, "tool not found")
}
var prompt = ""
func init() {
// 初始化提示词
prompt = fmt.Sprintf(__prompt, getToolPrompt(), fnTag, strings.Join(getToolNames(), ","), argsTag, resultTag, returnTag)
}
func GetPrompt() string {
return prompt
}
type ToolAiResult struct {
FnName string
Args string
}
// ParseResult 解析llm结果,有工具调用需求则会返回 ToolAiResult
func ParseResult(result string) *ToolAiResult {
f := strings.Index(result, fnTag)
i := strings.Index(result, argsTag)
j := strings.Index(result, resultTag)
k := strings.Index(result, returnTag)
if f == -1 {
return nil
}
if j == -1 {
j = k
if j == -1 {
j = len(result)
}
}
return &ToolAiResult{
FnName: strings.Trim(result[f+len(fnTag):i], ":\n "),
Args: strings.Trim(result[i+len(argsTag):j], ":\n "),
}
}
// CreateRoleMessage 创建角色消息
func CreateRoleMessage(msg string, role string) string {
return fmt.Sprintf("<|im_start|>%s\n%s<|im_end|>\n", role, msg)
}
// CreateRoleMessageNoEnd 创建角色消息 (不包含 end token)
func CreateRoleMessageNoEnd(msg string, role string) string {
return fmt.Sprintf("<|im_start|>%s\n%s", role, msg)
}
/* 这里封装一下 http请求 */
// CustomHTTPClient 是一个自定义的HTTP客户端结构
type CustomHTTPClient struct {
client *http.Client
}
// NewCustomHTTPClient 创建一个新的自定义HTTP客户端实例
func NewCustomHTTPClient(timeout time.Duration) *CustomHTTPClient {
return &CustomHTTPClient{
client: &http.Client{
Timeout: timeout,
},
}
}
// Do 发送一个HTTP Post 请求
func (c *CustomHTTPClient) Post(url string, data any, headers map[string]string) ([]byte, error) {
jsonData, err := json.Marshal(data) // 假设data是可以被json.Marshal序列化的
if err != nil {
return nil, fmt.Errorf("marshaling data: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("creating request: %w", err)
}
// 设置请求头
for key, value := range headers {
req.Header.Add(key, value)
}
// 使用上下文以控制超时
ctx, cancel := context.WithTimeout(context.Background(), c.client.Timeout)
defer cancel()
resp, err := c.client.Do(req.WithContext(ctx))
if err != nil {
return nil, fmt.Errorf("executing request: %w", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading response body: %w", err)
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("server returned error status: %d", resp.StatusCode)
}
return body, nil
}
/* 对接qwen API */
var client = NewCustomHTTPClient(120 * time.Second)
type QwenParamOptions struct {
ResultFormat string `json:"result_format"`
IncrementalOutput bool `json:"incremental_output,omitempty"` // true 表示只返回增量数据(用于流式返回
}
type QwenParamInput struct {
Prompt string `json:"prompt"`
}
type QwenParam struct {
Model string `json:"model"`
Parameters QwenParamOptions `json:"parameters"`
Input QwenParamInput `json:"input"`
}
type QwenResult struct {
Output struct {
Choices []struct {
FinishReason string `json:"finish_reason"`
Message struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"message"`
} `json:"choices"`
} `json:"output"`
Usage struct {
Plugins struct {
} `json:"plugins"`
TotalTokens int `json:"total_tokens"`
OutputTokens int `json:"output_tokens"`
InputTokens int `json:"input_tokens"`
} `json:"usage"`
RequestId string `json:"request_id"`
}
// RequestLLM
/**
快速发起 llm 请求
*/
func RequestLLM(message string) (string, error) {
body := QwenParam{
Model: "qwen-plus",
Parameters: QwenParamOptions{
ResultFormat: "message",
//IncrementalOutput: true,
},
Input: QwenParamInput{
Prompt: message,
},
}
res, err := client.Post("https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation", body, map[string]string{
"Content-Type": "application/json",
"Accept": "application/json", // 如果使用流式 text/event-stream
"Authorization": "Bearer " + QWEN_API_KEY,
//"X-DashScope-SSE": "enable", // 流式才需要打开
})
if err != nil {
return "", err
}
r := QwenResult{}
err = json.Unmarshal(res, &r)
if err != nil {
fmt.Println(string(res))
return "", err
}
if len(r.Output.Choices) > 0 {
return r.Output.Choices[0].Message.Content, nil
} else {
fmt.Println(string(res))
return "", fmt.Errorf("no choices")
}
}
func main() {
fmt.Print("assistant: 我是您终端助理,请告诉我您需要做什么?\nuser: ")
var prompt = CreateRoleMessage(GetPrompt(), "system")
for {
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
input = strings.TrimRight(input, "\r\n")
if input == "exit" {
break
}
prompt += CreateRoleMessage(input, "user")
prompt += CreateRoleMessageNoEnd("", "assistant")
for {
fmt.Printf("--------传入的prompt: \n%s\n\n", prompt)
result, err := RequestLLM(prompt)
fmt.Printf("--------LLM返回: \n%s\n", result)
if err != nil {
fmt.Println("assistant: ", err)
break
}
o := ParseResult(result)
if o == nil {
fmt.Println("assistant: ", result)
break
}
res := ExecFn(o.FnName, o.Args)
prompt += fmt.Sprintf("%s\n%s", result, res)
}
fmt.Print("\nuser: ")
}
}