Tools 格式说明文档请参见Tools 格式说明文档。
parse_product_info
),参数说明包含格式(如city: string
)和业务含义(如“目标城市拼音全称”),明确输出定义(如“返回JSON格式天气数据”)。get_product_detail
”)。StatusEnum
)避免无效参数,确保逻辑直观(最小惊讶原则)。submit_order
无需重复声明user_id
)。query_location
与mark_location
整合为query_and_mark_location
)。从 Doubao-1.5 系列模型开始支持流式输出,逐步获取工具调用信息,提升响应效率。
详见Function Calling 流式输出适配。
def function_calling_stream(): completion = client.chat.completions.create( model="doubao-1.5-pro-32k", messages=messages, tools=tools, stream=True ) for chunk in completion: if chunk.choices[0].delta.tool_calls: tool_call = chunk.choices[0].delta.tool_calls[0] print(f"工具名称:{tool_call.function.name},参数:{tool_call.function.arguments}") function_calling_stream()
当用户需求需要多次调用工具时,维护对话历史上下文,逐轮处理工具调用和结果回填。
get_current_weather
工具获取北京天气。send_message
工具将天气结果发送给张三。说明
多轮Function Calling:指用户query需要多次调用工具和大模型才能完成的情况, 是多轮对话的子集。
调用Response细节图:
package main import ( "context" "encoding/json" "fmt" "os" "strings" "github.com/volcengine/volcengine-go-sdk/service/arkruntime" "github.com/volcengine/volcengine-go-sdk/service/arkruntime/model" "github.com/volcengine/volcengine-go-sdk/volcengine" ) func main() { client := arkruntime.NewClientWithApiKey( os.Getenv("ARK_API_KEY"), arkruntime.WithBaseUrl("${BASE_URL}"), ) fmt.Println("----- function call mulstiple rounds request -----") ctx := context.Background() // Step 1: send the conversation and available functions to the model req := model.CreateChatCompletionRequest{ Model: "${Model_ID}", Messages: []*model.ChatCompletionMessage{ { Role: model.ChatMessageRoleSystem, Content: &model.ChatCompletionMessageContent{ StringValue: volcengine.String("你是豆包,是由字节跳动开发的 AI 人工智能助手"), }, }, { Role: model.ChatMessageRoleUser, Content: &model.ChatCompletionMessageContent{ StringValue: volcengine.String("上海的天气怎么样?"), }, }, }, Tools: []*model.Tool{ { Type: model.ToolTypeFunction, Function: &model.FunctionDefinition{ Name: "get_current_weather", Description: "Get the current weather in a given location", Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "location": map[string]interface{}{ "type": "string", "description": "The city and state, e.g. Beijing", }, "unit": map[string]interface{}{ "type": "string", "description": "枚举值有celsius、fahrenheit", }, }, "required": []string{ "location", }, }, }, }, }, } resp, err := client.CreateChatCompletion(ctx, req) if err != nil { fmt.Printf("chat error: %v\n", err) return } // extend conversation with assistant's reply req.Messages = append(req.Messages, &resp.Choices[0].Message) // Step 2: check if the model wanted to call a function. // The model can choose to call one or more functions; if so, // the content will be a stringified JSON object adhering to // your custom schema (note: the model may hallucinate parameters). for _, toolCall := range resp.Choices[0].Message.ToolCalls { fmt.Println("calling function") fmt.Println(" id:", toolCall.ID) fmt.Println(" name:", toolCall.Function.Name) fmt.Println(" argument:", toolCall.Function.Arguments) functionResponse, err := CallAvailableFunctions(toolCall.Function.Name, toolCall.Function.Arguments) if err != nil { functionResponse = err.Error() } // extend conversation with function response req.Messages = append(req.Messages, &model.ChatCompletionMessage{ Role: model.ChatMessageRoleTool, ToolCallID: toolCall.ID, Content: &model.ChatCompletionMessageContent{ StringValue: &functionResponse, }, }, ) } // get a new response from the model where it can see the function response secondResp, err := client.CreateChatCompletion(ctx, req) if err != nil { fmt.Printf("second chat error: %v\n", err) return } fmt.Println("conversation", MustMarshal(req.Messages)) fmt.Println("new message", MustMarshal(secondResp.Choices[0].Message)) } func CallAvailableFunctions(name, arguments string) (string, error) { if name == "get_current_weather" { params := struct { Location string `json:"location"` Unit string `json:"unit"` }{} if err := json.Unmarshal([]byte(arguments), ¶ms); err != nil { return "", fmt.Errorf("failed to parse function call name=%s arguments=%s", name, arguments) } return GetCurrentWeather(params.Location, params.Unit), nil } else { return "", fmt.Errorf("got unavailable function name=%s arguments=%s", name, arguments) } } // GetCurrentWeather get the current weather in a given location. // Example dummy function hard coded to return the same weather. // In production, this could be your backend API or an external API func GetCurrentWeather(location, unit string) string { if unit == "" { unit = "celsius" } switch strings.ToLower(location) { case "beijing": return `{"location": "Beijing", "temperature": "10", "unit": unit}` case "北京": return `{"location": "Beijing", "temperature": "10", "unit": unit}` case "shanghai": return `{"location": "Shanghai", "temperature": "23", "unit": unit})` case "上海": return `{"location": "Shanghai", "temperature": "23", "unit": unit})` default: return fmt.Sprintf(`{"location": %s, "temperature": "unknown"}`, location) } } func MustMarshal(v interface{}) string { b, _ := json.Marshal(v) return string(b) }
from volcenginesdkarkruntime import Ark import time client = Ark( base_url="${BASE_URL}", ) print("----- function call mulstiple rounds request -----") messages = [ { "role": "system", "content": "你是豆包,是由字节跳动开发的 AI 人工智能助手", }, { "role": "user", "content": "北京今天的天气", }, ] req = { "model": "${YOUR_ENDPOINT_ID}", "messages": messages, "temperature": 0.8, "tools": [ { "type": "function", "function": { "name": "MusicPlayer", "description": """歌曲查询Plugin,当用户需要搜索某个歌手或者歌曲时使用此plugin,给定歌手,歌名等特征返回相关音乐。\n 例子1:query=想听孙燕姿的遇见, 输出{"artist":"孙燕姿","song_name":"遇见","description":""}""", "parameters": { "properties": { "artist": {"description": "表示歌手名字", "type": "string"}, "description": { "description": "表示描述信息", "type": "string", }, "song_name": { "description": "表示歌曲名字", "type": "string", }, }, "required": [], "type": "object", }, }, }, { "type": "function", "function": { "name": "get_current_weather", "description": "", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "地理位置,比如北京市", }, "unit": {"type": "string", "description": "枚举值 [摄氏度,华氏度]"}, }, "required": ["location"], }, }, }, ], } ts = time.time() completion = client.chat.completions.create(**req) if completion.choices[0].message.tool_calls: print( f"Bot [{time.time() - ts:.3f} s][Use FC]: ", completion.choices[0].message.tool_calls[0], ) # ========== 补充函数调用的结果 ========= req["messages"].extend( [ completion.choices[0].message.dict(), { "role": "tool", "tool_call_id": completion.choices[0].message.tool_calls[0].id, "content": "北京天气晴,24~30度", # 根据实际调用函数结果填写,最好用自然语言。 "name": completion.choices[0].message.tool_calls[0].function.name, }, ] ) # 再请求一次模型,获得总结。 如不需要,也可以省略 ts = time.time() completion = client.chat.completions.create(**req) print( f"Bot [{time.time() - ts:.3f} s][FC Summary]: ", completion.choices[0].message.content, )
package com.volcengine.ark.runtime; import com.volcengine.ark.runtime.model.completion.chat.*; import com.volcengine.ark.runtime.service.ArkService; import okhttp3.ConnectionPool; import okhttp3.Dispatcher; import java.util.*; import java.util.concurrent.TimeUnit; public class FunctionCallChatCompletionsExample { static String apiKey = System.getenv("ARK_API_KEY"); static ConnectionPool connectionPool = new ConnectionPool(5, 1, TimeUnit.SECONDS); static Dispatcher dispatcher = new Dispatcher(); static ArkService service = ArkService.builder().dispatcher(dispatcher).connectionPool(connectionPool).baseUrl("${BASE_URL}").apiKey(apiKey).build(); public static void main(String[] args) { System.out.println("\n----- function call mulstiple rounds request -----"); final List<ChatMessage> messages = new ArrayList<>(); final ChatMessage userMessage = ChatMessage.builder().role(ChatMessageRole.USER).content("北京今天天气如何?").build(); messages.add(userMessage); final List<ChatTool> tools = Arrays.asList( new ChatTool( "function", new ChatFunction.Builder() .name("get_current_weather") .description("获取给定地点的天气") .parameters(new Weather( "object", new HashMap<String, Object>() {{ put("location", new HashMap<String, String>() {{ put("type", "string"); put("description", "T地点的位置信息,比如北京"); }}); put("unit", new HashMap<String, Object>() {{ put("type", "string"); put("description", "枚举值有celsius、fahrenheit"); }}); }}, Collections.singletonList("location") )) .build() ) ); ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() .model("${YOUR_ENDPOINT_ID}") .messages(messages) .tools(tools) .build(); ChatCompletionChoice choice = service.createChatCompletion(chatCompletionRequest).getChoices().get(0); messages.add(choice.getMessage()); choice.getMessage().getToolCalls().forEach( toolCall -> { messages.add(ChatMessage.builder().role(ChatMessageRole.TOOL).toolCallId(toolCall.getId()).content("北京天气晴,24~30度").name(toolCall.getFunction().getName()).build()); }); ChatCompletionRequest chatCompletionRequest2 = ChatCompletionRequest.builder() .model("${YOUR_ENDPOINT_ID}") .messages(messages) .build(); service.createChatCompletion(chatCompletionRequest2).getChoices().forEach(System.out::println); // shutdown service service.shutdownExecutor(); } public static class Weather { public String type; public Map<String, Object> properties; public List<String> required; public Weather(String type, Map<String, Object> properties, List<String> required) { this.type = type; this.properties = properties; this.required = required; } public String getType() { return type; } public void setType(String type) { this.type = type; } public Map<String, Object> getProperties() { return properties; } public void setProperties(Map<String, Object> properties) { this.properties = properties; } public List<String> getRequired() { return required; } public void setRequired(List<String> required) { this.required = required; } } }
========== Round 1 ========== user: 先查询北京的天气,如果是晴天微信发给Alan,否则发给Peter... assistant [FC Response]: name=GetCurrentWeather, args={"location": "\u5317\u4eac"} [elpase=2.607 s] ========== Round 2 ========== tool: 北京今天20~24度,天气:阵雨。... assistant [FC Response]: name=SendMessage, args={"content": "\u4eca\u5929\u5317\u4eac\u7684\u5929\u6c14", "receiver": "Peter"} [elpase=3.492 s] ========== Round 3 ========== tool: 成功发送微信消息至Peter... assistant [Final Answer]: 好的,请问还有什么可以帮助您? [elpase=0.659 s]
JSON 格式容错机制:对于轻微不合法的 JSON 格式,可尝试使用 json-repair
库进行容错修复。
import json_repair invalid_json = '{"location": "北京", "unit": 摄氏度}' valid_json = json_repair.loads(invalid_json)
需求澄清(确认需求),不依赖与FC,可独立使用。
可在 System prompt 中加入:
如果用户没有提供足够的信息来调用函数,请继续提问以确保收集到了足够的信息。 在调用函数之前,你必须总结用户的描述并向用户提供总结,询问他们是否需要进行任何修改。 ......
在函数的 description 中加入:
函数参数除了提取a和b, 还应要求用户提供c、d、e、f和其他相关细节。
或在系统提示中加入参数校验逻辑,当模型生成的参数缺失时,引导模型重新生成完整的参数。
如果用户提供的信息缺少工具所需要的必填参数,你需要进一步追问让用户提供更多信息。
原则: Treat LLM as a kid
类别 | 问题 | 错误示例 | 改正后示例 |
---|---|---|---|
函数 | 命名不规范、描述不规范 |
|
|
参数 | 避免不必要的复杂格式(或嵌套) |
|
|
避免固定值 |
| 既然参数值固定,删去该参数,由代码处理。 | |
业务流程 | 尽量缩短LLM调用轮次 | System prompt:
| System prompt:
|
歧义消解 | System prompt:
这里两个ID未明确,模型可能会混用 | System prompt:
|
如果 Function Calling 效果未达预期,可通过精调(SFT)提升模型表现。
详情请参见创建模型精调任务。
{ "messages": [ { "role": "system", "content": "你是豆包AI助手" }, { "role": "user", "content": "把北京的天气发给李四" }, { "role": "assistant", "content": "", "tool_calls": [ { "type": "function", "function": { "name": "get_current_weather", "arguments": "{\"location\": \"北京\"}" } } ], "loss_weight": 1.0 }, { "role": "tool", "content": "北京今天晴,25摄氏度" }, { "role": "assistant", "content": "", "tool_calls": [ { "type": "function", "function": { "name": "send_message", "arguments": "{\"receiver\": \"李四\", \"content\": \"北京今天晴,25摄氏度\"}" } } ], "loss_weight": 1.0 }, { "role": "tool", "content": "消息发送成功" }, { "role": "assistant", "content": "已将北京的天气信息发送给李四" } ], "tools": [ { "type": "function", "function": { "name": "get_current_weather", "description": "获取指定地点的天气", "parameters": { "type": "object", "properties": { "location": {"type": "string", "description": "地点的位置信息"} }, "required": ["location"] } } } ] }