二、适用场景
适用于慢思考(high )+ 工具调用(tool call)的跨轮次场景,例如:
·Agent 长流程任务:多轮工具编排与信息汇总;
·Coding 类任务:多轮代码补全 / 修改 / 调试,需延续上一轮的推理路径;
·多轮对话中含追问:用户在同一会话中提出关联问题,需要模型基于先前思考继续回答。
此场景下的调用模式如下:
三、与交错式思考的差异
两者均围绕“保留 reasoning_content 以延续思维链”这一核心机制,主要差异在于保留的范围不同。开发者对两种模式的操作方式是一致的——都是把模型返回的 reasoning_content 原样回填到下一次请求。
| 概念 | 思考内容的保留范围 | 通俗理解 |
| 交错式思考(Interleaved) | 同一个用户提问轮(User turn)内 | 类似做同一道题时中途查资料:模型先写一段解题草稿,开发者在工具结果返回时把这份草稿原样带回,模型才能接着刚才的思路继续做。本轮结束后,下一轮用户提问不再带回这道题的草稿。 |
| 保留式思考(Preserved) | 跨用户提问轮(User turn) | 类似把每道题的解题草稿都收好:后续用户继续追问时,开发者把相关历史草稿原样带回请求里,模型继续参考回答。 |
关于交错式思考的完整说明,请参见 交错式思考模式(Interleaved Thinking)。
四、支持模型与协议
·支持模型:hy3 、hy4-preview、hy4
·支持协议:OpenAI Chat Completions、OpenAI Responses、Anthropic 三种协议均支持,开关方式略有差异(详见下文“开关方式”)。
五、开关方式
保留式思考支持通过参数显式开启或关闭,各协议开关字段如下:
| 协议 | 开关方式 | 参考示例 |
| OpenAI Chat Completions | 请求体顶层字段 preserved_thinking | {"reasoning_effort":"high","preserved_thinking":true} |
| OpenAI Responses | reasoning 对象内字段 preserved_thinking | {"reasoning":{"effort":"high","preserved_thinking":true}} |
| Anthropic | 请求头 HY-Preserved-Thinking取值为 true / false / 为空或不传:明确传 true 为开启、false 为关闭;为空或不传时采用 API 默认策略;传入非法值会报错。 | HY-Preserved-Thinking: true |
注意:建议:
注意:1. 如无特殊需求,不建议手动传 preserved_thinking,平台会自动采用最优策略处理。
注意:2. 仅在下述特殊情形下再考虑手动开启:工具定义未通过标准 tools 数组注册(如写在 system prompt 中),此时平台无法自动识别为工具调用场景,可手动设置 preserved_thinking: true。更推荐的做法是仍将工具按标准方式注册到 tools 数组中,既能复用平台策略,也能获得最佳的模型效果。
六、字段回填要求
注意:preserved_thinking 参数仅控制保留式思考的启停,不会自动处理历史 reasoning_content 的回填。开发者需要将模型上一轮返回的完整、未经修改的 reasoning_content 与工具结果一并原样回填给 API,否则可能影响模型效果,并降低缓存命中率。
七、常用字段
以 Chat Completions 协议为例:
| 字段 | 位置 | 说明 |
| reasoning_effort | 请求 | 思考深度,取值 no_think / high |
| reasoning_content | 响应(需回填) | 模型的思考过程,需在后续请求中原样回填 |
| tool_calls | 响应(需回填) | 模型输出的工具调用指令 |
| content | 响应 | 最终回答内容 |
八、回填规则(务必遵守)
·原样回填:回填的 reasoning_content 必须与模型生成时完全一致,不要改写、截断或重新排序,否则会降低效果并影响缓存命中率。
·跟随对应的 assistant 消息:把 reasoning_content 放回它所属的那条 assistant 消息中(与 content、tool_calls 同级)。
·持续保留:在拿到最终回答前的每一次请求,都要带上历史的 reasoning_content。
九、调用流程
以 Chat Completions 协议为例,在慢思考 + 工具调用场景下,建议严格遵循此调用流程:
step1:发起首次请求(包含 system/user 内容与 tools 列表),保留式思考默认开启。
step2:API 输出 assistant 消息,其中可能包含:
·reasoning_content
·tool_calls
step3:业务方执行工具。
step4:将工具结果以 role=tool 回填,并将上一步 assistant 消息(含 reasoning_content 与 tool_calls)原样加入 messages 数组,再次发起请求。
step5:模型可能继续发起新的 tool call,或输出最终答案。若继续 tool call,则重复 step3~4。
step6:用户发起下一轮追问时(跨用户提问轮),同样需要将此前所有轮次的 assistant 消息(含 reasoning_content)原样保留在 messages 中,这是保留式思考与交错式思考最核心的差异。
十、调用示例(慢思考 + 多步工具调用)
以下以 Chat Completions 协议为例,提供一个简单的保留式思考示例。
场景:一个企业 IT 权限助理 Agent,提供两个工具:get_user_groups(查用户所属的组)、get_group_permissions(查某个组对各资源的权限)。用户分三轮提问,每一轮都保留此前轮的思考:
·第 1 轮用户输入:Alice 属于哪些用户组?
·第 2 轮用户输入:这些组分别有什么权限?
·第 3 轮用户输入:综合看,Alice 对"生产数据库"到底是什么权限?
注意:说明:
注意:1. 对于保留式思考,通常难以在简单任务上产生显著效果增益(在多轮、长流程的复杂任务上,保留式思考的效果和成本增益会更加显著),因此此示例仅用于演示跨轮调用的使用方式。
注意:2. 请你将示例代码中的 YOUR_API_KEY 替换为你真实的 API Key。如你还没有 API Key,请参见 创建 API Key。
十一、1. 【第 1 轮】首次请求(用户第一次提问)
请求:
cURLPythonNode.jsJavaGocurl -X POST 'https://tokenhub.tencentmaas.com/v1/chat/completions' \\ -H 'Content-Type: application/json' \\ -H 'Authorization: Bearer YOUR_API_KEY' \\ -d '{ "model": "hy4", "stream": false, "reasoning_effort": "high", "tool_choice": "auto", "messages": [ { "role": "system", "content": "你是一个企业 IT 权限助理 Agent,通过调用工具查询组织与权限数据来回答问题。请始终用中文进行逐步推理与思考。" }, { "role": "user", "content": "Alice 属于哪些用户组?" } ], "tools": [ { "type": "function", "function": { "name": "get_user_groups", "description": "查询某个用户所属的所有用户组。", "parameters": { "type": "object", "properties": { "user": { "type": "string" } }, "required": ["user"] } } }, { "type": "function", "function": { "name": "get_group_permissions", "description": "查询某个用户组对各资源的访问权限。", "parameters": { "type": "object", "properties": { "group": { "type": "string" } }, "required": ["group"] } } } ] }'from openai import OpenAIclient = OpenAI( api_key="YOUR_API_KEY", base_url="https://tokenhub.tencentmaas.com/v1",)tools = [ { "type": "function", "function": { "name": "get_user_groups", "description": "查询某个用户所属的所有用户组。", "parameters": { "type": "object", "properties": { "user": {"type": "string"}, }, "required": ["user"], }, }, }, { "type": "function", "function": { "name": "get_group_permissions", "description": "查询某个用户组对各资源的访问权限。", "parameters": { "type": "object", "properties": { "group": {"type": "string"}, }, "required": ["group"], }, }, },]messages = [ {"role": "system", "content": "你是一个企业 IT 权限助理 Agent,通过调用工具查询组织与权限数据来回答问题。请始终用中文进行逐步推理与思考。"}, {"role": "user", "content": "Alice 属于哪些用户组?"},]# 携带 tools 时,hy4 默认开启保留式思考。# 如需显式控制,可在 extra_body 中传 preserved_thinking: True/Falseresp1 = client.chat.completions.create( model="hy4", messages=messages, tools=tools, tool_choice="auto", extra_body={"reasoning_effort": "high"},)msg1 = resp1.choices[0].messageprint("第 1 轮 reasoning_content:", getattr(msg1, "reasoning_content", ""))print("第 1 轮 tool_calls:", msg1.tool_calls)import OpenAI from 'openai';const client = new OpenAI({ apiKey: 'YOUR_API_KEY', baseURL: 'https://tokenhub.tencentmaas.com/v1',});const tools = [ { type: 'function', function: { name: 'get_user_groups', description: '查询某个用户所属的所有用户组。', parameters: { type: 'object', properties: { user: { type: 'string' }, }, required: ['user'], }, }, }, { type: 'function', function: { name: 'get_group_permissions', description: '查询某个用户组对各资源的访问权限。', parameters: { type: 'object', properties: { group: { type: 'string' }, }, required: ['group'], }, }, },];const messages = [ { role: 'system', content: '你是一个企业 IT 权限助理 Agent,通过调用工具查询组织与权限数据来回答问题。请始终用中文进行逐步推理与思考。' }, { role: 'user', content: 'Alice 属于哪些用户组?' },];const resp1 = await client.chat.completions.create({ model: 'hy4', messages, tools, tool_choice: 'auto', reasoning_effort: 'high',});const msg1 = resp1.choices[0].message;console.log('第 1 轮 reasoning_content:', msg1.reasoning_content);console.log('第 1 轮 tool_calls:', msg1.tool_calls);import okhttp3.*;import com.google.gson.*;import java.util.*;public class PreservedThinking { static final String URL = "https://tokenhub.tencentmaas.com/v1/chat/completions"; static final String API_KEY = "YOUR_API_KEY"; static final OkHttpClient HTTP = new OkHttpClient(); static final Gson GSON = new Gson(); /** 通用 chat 调用,返回原始 JSON 响应字符串。 */ static String chat(List<Map<String, Object>> messages, List<Map<String, Object>> tools) throws Exception { Map<String, Object> body = new HashMap<>(); body.put("model", "hy4"); body.put("messages", messages); body.put("tools", tools); body.put("tool_choice", "auto"); body.put("reasoning_effort", "high"); body.put("stream", false); // 显式控制可加:body.put("preserved_thinking", true); Request req = new Request.Builder() .url(URL) .header("Authorization", "Bearer " + API_KEY) .post(RequestBody.create(GSON.toJson(body), MediaType.parse("application/json"))) .build(); try (Response resp = HTTP.newCall(req).execute()) { return resp.body().string(); } } public static void main(String[] args) throws Exception { List<Map<String, Object>> tools = List.of( Map.of( "type", "function", "function", Map.of( "name", "get_user_groups", "description", "查询某个用户所属的所有用户组。", "parameters", Map.of( "type", "object", "properties", Map.of( "user", Map.of("type", "string") ), "required", List.of("user") ) ) ), Map.of( "type", "function", "function", Map.of( "name", "get_group_permissions", "description", "查询某个用户组对各资源的访问权限。", "parameters", Map.of( "type", "object", "properties", Map.of( "group", Map.of("type", "string") ), "required", List.of("group") ) ) ) ); List<Map<String, Object>> messages = new ArrayList<>(); messages.add(Map.of("role", "system", "content", "你是一个企业 IT 权限助理 Agent,通过调用工具查询组织与权限数据来回答问题。请始终用中文进行逐步推理与思考。")); messages.add(Map.of("role", "user", "content", "Alice 属于哪些用户组?")); // 第 1 轮:模型决定是否调用工具 String r1 = chat(messages, tools); System.out.println("第 1 轮响应:" + r1); // 后续按响应里的 reasoning_content / tool_calls 回填到 messages,见第 2 步 }}package mainimport ( "bytes" "encoding/json" "fmt" "io" "net/http")const ( URL = "https://tokenhub.tencentmaas.com/v1/chat/completions" APIKEY = "YOUR_API_KEY")// 通用 chat 调用func chat(messages []map[string]interface{}, tools []map[string]interface{}) (map[string]interface{}, error) { body, _ := json.Marshal(map[string]interface{}{ "model": "hy4", "messages": messages, "tools": tools, "tool_choice": "auto", "reasoning_effort": "high", "stream": false, // 显式控制可加:"preserved_thinking": true, }) req, _ := http.NewRequest("POST", URL, bytes.NewBuffer(body)) req.Header.Set("Authorization", "Bearer "+APIKEY) req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() data, _ := io.ReadAll(resp.Body) var out map[string]interface{} json.Unmarshal(data, &out) return out, nil}func main() { tools := []map[string]interface{}{ { "type": "function", "function": map[string]interface{}{ "name": "get_user_groups", "description": "查询某个用户所属的所有用户组。", "parameters": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "user": map[string]string{"type": "string"}, }, "required": []string{"user"}, }, }, }, { "type": "function", "function": map[string]interface{}{ "name": "get_group_permissions", "description": "查询某个用户组对各资源的访问权限。", "parameters": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "group": map[string]string{"type": "string"}, }, "required": []string{"group"}, }, }, }, } messages := []map[string]interface{}{ {"role": "system", "content": "你是一个企业 IT 权限助理 Agent,通过调用工具查询组织与权限数据来回答问题。请始终用中文进行逐步推理与思考。"}, {"role": "user", "content": "Alice 属于哪些用户组?"}, } // 第 1 轮:模型决定是否调用工具 r1, _ := chat(messages, tools) fmt.Printf("第 1 轮响应: %+v\\n", r1) // 后续按响应里的 reasoning_content / tool_calls 回填到 messages,见第 2 步}
响应:模型先思考,并决定调用 get_user_groups。
{ "id": "REPLACED_ID", "object": "chat.completion", "created": 1783084572, "model": "hy4", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "我来查询 Alice 所属的用户组。", "reasoning_content": "用户问 Alice 属于哪些用户组。我需要调用 get_user_groups 工具来查询 Alice 所属的用户组。只有一个调用,没有依赖。", "tool_calls": [ { "id": "chatcmpl-tool-93cc85d5f381f88d", "type": "function", "function": { "name": "get_user_groups", "arguments": "{\\"user\\": \\"Alice\\"}" } } ] }, "finish_reason": "tool_calls" } ], "usage": { "prompt_tokens": 289, "completion_tokens": 60, "total_tokens": 349, "completion_tokens_details": { "reasoning_tokens": 31 } }}
十二、2. 【第 1 轮】回填工具结果,延续思考
把上一步 assistant 消息(含 reasoning_content)与工具结果一起回填。假设 get_user_groups 返回 ["Engineering","OnCall"]:
请求:
cURLPythonNode.jsJavaGocurl -X POST 'https://tokenhub.tencentmaas.com/v1/chat/completions' \\ -H 'Content-Type: application/json' \\ -H 'Authorization: Bearer YOUR_API_KEY' \\ -d '{ "model": "hy4", "stream": false, "reasoning_effort": "high", "tool_choice": "auto", "messages": [ { "role": "system", "content": "你是一个企业 IT 权限助理 Agent,通过调用工具查询组织与权限数据来回答问题。请始终用中文进行逐步推理与思考。" }, { "role": "user", "content": "Alice 属于哪些用户组?" }, { "role": "assistant", "content": "我来查询 Alice 所属的用户组。", "reasoning_content": "用户问 Alice 属于哪些用户组。我需要调用 get_user_groups 工具来查询 Alice 所属的用户组。只有一个调用,没有依赖。", "tool_calls": [ { "id": "chatcmpl-tool-93cc85d5f381f88d", "type": "function", "function": { "name": "get_user_groups", "arguments": "{\\"user\\": \\"Alice\\"}" } } ] }, { "role": "tool", "tool_call_id": "chatcmpl-tool-93cc85d5f381f88d", "content": "{\\"user\\": \\"Alice\\", \\"groups\\": [\\"Engineering\\", \\"OnCall\\"]}" } ], "tools": [ /* 同上,省略 */ ] }'# 接续 step1:把第 1 轮的 assistant 消息(含 reasoning_content + tool_calls)# 与工具结果一起回填到 messagesimport json# 第 1 轮 assistant 消息的回写:reasoning_content 必须保留assistant_msg = { "role": "assistant", "content": msg1.content or "", "reasoning_content": getattr(msg1, "reasoning_content", ""), "tool_calls": [ { "id": tc.id, "type": tc.type, "function": { "name": tc.function.name, "arguments": tc.function.arguments, }, } for tc in (msg1.tool_calls or []) ],}messages.append(assistant_msg)# 业务侧执行 get_user_groups,把结果以 role=tool 回填for tc in (msg1.tool_calls or []): # 这里替换为真实业务逻辑 tool_result = json.dumps({"user": "Alice", "groups": ["Engineering", "OnCall"]}, ensure_ascii=False) messages.append({ "role": "tool", "tool_call_id": tc.id, "content": tool_result, })# 第 2 轮:把工具结果送回模型,模型继续思考resp2 = client.chat.completions.create( model="hy4", messages=messages, tools=tools, tool_choice="auto", extra_body={"reasoning_effort": "high"},)msg2 = resp2.choices[0].messageprint("第 2 轮 reasoning_content:", getattr(msg2, "reasoning_content", ""))print("第 2 轮 content:", msg2.content)// 接续 step1:把第 1 轮 assistant 消息与工具结果回填const assistantMsg = { role: 'assistant', content: msg1.content || '', reasoning_content: msg1.reasoning_content, tool_calls: msg1.tool_calls,};messages.push(assistantMsg);for (const tc of msg1.tool_calls || []) { // 这里替换为真实业务逻辑 const toolResult = JSON.stringify({ user: 'Alice', groups: ['Engineering', 'OnCall'] }); messages.push({ role: 'tool', tool_call_id: tc.id, content: toolResult, });}const resp2 = await client.chat.completions.create({ model: 'hy4', messages, tools, tool_choice: 'auto', reasoning_effort: 'high',});const msg2 = resp2.choices[0].message;console.log('第 2 轮 reasoning_content:', msg2.reasoning_content);console.log('第 2 轮 content:', msg2.content);// 接续 main():把第 1 轮 assistant 消息与工具结果回填,再发起第 2 轮请求// 完整流程示意(仅展示消息构造,HTTP 调用复用 chat() 函数)// 1. 解析第 1 轮响应JsonObject r1Obj = JsonParser.parseString(r1).getAsJsonObject();JsonObject msg1 = r1Obj.getAsJsonArray("choices").get(0).getAsJsonObject() .getAsJsonObject("message");// 2. 把 assistant 消息(含 reasoning_content)整体回写到 messagesMap<String, Object> assistantEntry = new LinkedHashMap<>();assistantEntry.put("role", "assistant");assistantEntry.put("content", msg1.has("content") ? msg1.get("content").getAsString() : "");if (msg1.has("reasoning_content")) { assistantEntry.put("reasoning_content", msg1.get("reasoning_content").getAsString());}if (msg1.has("tool_calls")) { assistantEntry.put("tool_calls", GSON.fromJson(msg1.get("tool_calls"), List.class));}messages.add(assistantEntry);// 3. 业务侧执行 get_user_groups,把结果以 role=tool 回填for (JsonElement el : msg1.getAsJsonArray("tool_calls")) { JsonObject call = el.getAsJsonObject(); // 这里替换为真实业务逻辑 String toolResult = "{\\"user\\": \\"Alice\\", \\"groups\\": [\\"Engineering\\", \\"OnCall\\"]}"; messages.add(Map.of( "role", "tool", "tool_call_id", call.get("id").getAsString(), "content", toolResult ));}// 4. 第 2 轮:把工具结果送回模型String r2 = chat(messages, tools);System.out.println("第 2 轮响应:" + r2);// 接续 main():把第 1 轮 assistant 消息与工具结果回填,再发起第 2 轮请求// 1. 从第 1 轮响应中取出 assistant 消息msg1Wrap := r1["choices"].([]interface{})[0].(map[string]interface{})msg1 := msg1Wrap["message"].(map[string]interface{})// 2. 把 assistant 消息(含 reasoning_content)整体回写到 messagesmessages = append(messages, msg1)// 3. 业务侧执行 get_user_groups,把结果以 role=tool 回填toolCalls, _ := msg1["tool_calls"].([]interface{})for _, c := range toolCalls { call := c.(map[string]interface{}) // 这里替换为真实业务逻辑 toolResult := `{"user": "Alice", "groups": ["Engineering", "OnCall"]}` messages = append(messages, map[string]interface{}{ "role": "tool", "tool_call_id": call["id"], "content": toolResult, })}// 4. 第 2 轮:把工具结果送回模型r2, _ := chat(messages, tools)fmt.Printf("第 2 轮响应: %+v\\n", r2)
响应:模型本轮回答完成,finish_reason=stop:
{ "id": "REPLACED_ID", "object": "chat.completion", "created": 1783084576, "model": "hy4", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "根据查询结果,Alice 属于以下两个用户组:\\n\\n- **Engineering**\\n- **OnCall**\\n\\n所以 Alice 同时是 Engineering 组和 OnCall 组的成员。", "reasoning_content": "根据工具返回结果,Alice 属于 Engineering 和 OnCall 两个用户组。" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 375, "completion_tokens": 54, "total_tokens": 429, "prompt_tokens_details": { "cached_tokens": 320 }, "completion_tokens_details": { "reasoning_tokens": 18 } }}
十三、3. 【第 2 轮】新一轮用户提问,保留此前思考
追加第 2 个 user 提问,并把此前轮次的 reasoning_content 等上下文原样保留再次发起请求:
请求:
cURLPythonNode.jsJavaGocurl -X POST 'https://tokenhub.tencentmaas.com/v1/chat/completions' \\ -H 'Content-Type: application/json' \\ -H 'Authorization: Bearer YOUR_API_KEY' \\ -d '{ "model": "hy4", "stream": false, "reasoning_effort": "high", "tool_choice": "auto", "messages": [ { "role": "system", "content": "你是一个企业 IT 权限助理 Agent,通过调用工具查询组织与权限数据来回答问题。请始终用中文进行逐步推理与思考。" }, { "role": "user", "content": "Alice 属于哪些用户组?" }, { "role": "assistant", "content": "我来查询 Alice 所属的用户组。", "reasoning_content": "用户问 Alice 属于哪些用户组。我需要调用 get_user_groups 工具来查询 Alice 所属的用户组。只有一个调用,没有依赖。", "tool_calls": [ { "id": "chatcmpl-tool-93cc85d5f381f88d", "type": "function", "function": { "name": "get_user_groups", "arguments": "{\\"user\\": \\"Alice\\"}" } } ] }, { "role": "tool", "tool_call_id": "chatcmpl-tool-93cc85d5f381f88d", "content": "{\\"user\\": \\"Alice\\", \\"groups\\": [\\"Engineering\\", \\"OnCall\\"]}" }, { "role": "assistant", "content": "根据查询结果,Alice 属于以下两个用户组:\\n\\n- **Engineering**\\n- **OnCall**\\n\\n所以 Alice 同时是 Engineering 组和 OnCall 组的成员。", "reasoning_content": "根据工具返回结果,Alice 属于 Engineering 和 OnCall 两个用户组。" }, { "role": "user", "content": "这些组分别有哪些资源的访问权限?" } ], "tools": [ /* 同上,省略 */ ] }'# 接续 step2:把第 2 轮 assistant 消息(含 reasoning_content)追加到 messages,# 然后追加第 2 轮用户提问,再次发起请求messages.append({ "role": "assistant", "content": msg2.content or "", "reasoning_content": getattr(msg2, "reasoning_content", ""),})messages.append({"role": "user", "content": "这些组分别有哪些资源的访问权限?"})resp3 = client.chat.completions.create( model="hy4", messages=messages, tools=tools, tool_choice="auto", extra_body={"reasoning_effort": "high"},)msg3 = resp3.choices[0].messageprint("第 3 轮 reasoning_content:", getattr(msg3, "reasoning_content", ""))print("第 3 轮 tool_calls:", msg3.tool_calls)// 接续 step2:把第 2 轮 assistant 消息(含 reasoning_content)追加到 messages,// 然后追加第 2 轮用户提问,再次发起请求messages.push({ role: 'assistant', content: msg2.content || '', reasoning_content: msg2.reasoning_content,});messages.push({ role: 'user', content: '这些组分别有哪些资源的访问权限?' });const resp3 = await client.chat.completions.create({ model: 'hy4', messages, tools, tool_choice: 'auto', reasoning_effort: 'high',});const msg3 = resp3.choices[0].message;console.log('第 3 轮 reasoning_content:', msg3.reasoning_content);console.log('第 3 轮 tool_calls:', msg3.tool_calls);// 接续第 2 轮响应 r2:把第 2 轮 assistant 消息(含 reasoning_content)追加到 messagesJsonObject r2Obj = JsonParser.parseString(r2).getAsJsonObject();JsonObject msg2 = r2Obj.getAsJsonArray("choices").get(0).getAsJsonObject() .getAsJsonObject("message");Map<String, Object> assistant2 = new LinkedHashMap<>();assistant2.put("role", "assistant");assistant2.put("content", msg2.has("content") ? msg2.get("content").getAsString() : "");if (msg2.has("reasoning_content")) { assistant2.put("reasoning_content", msg2.get("reasoning_content").getAsString());}messages.add(assistant2);// 追加第 2 轮用户提问messages.add(Map.of("role", "user", "content", "这些组分别有哪些资源的访问权限?"));// 第 3 轮:把新提问送回模型String r3 = chat(messages, tools);System.out.println("第 3 轮响应:" + r3);// 接续第 2 轮响应 r2:把第 2 轮 assistant 消息(含 reasoning_content)追加到 messagesmsg2Wrap := r2["choices"].([]interface{})[0].(map[string]interface{})msg2 := msg2Wrap["message"].(map[string]interface{})messages = append(messages, msg2)// 追加第 2 轮用户提问messages = append(messages, map[string]interface{}{ "role": "user", "content": "这些组分别有哪些资源的访问权限?",})// 第 3 轮:把新提问送回模型r3, _ := chat(messages, tools)fmt.Printf("第 3 轮响应: %+v\\n", r3)
响应:模型延续之前的思路,发起 get_group_permissions 调用(本轮并行发起两个工具调用):
{ "id": "REPLACED_ID", "object": "chat.completion", "created": 1783084580, "model": "hy4", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "我来查询这两个用户组各自的资源访问权限。", "reasoning_content": "用户想知道 Alice 所属的两个组(Engineering 和 OnCall)分别有哪些资源的访问权限。我需要调用 get_group_permissions 工具来查询这两个组的权限。这两个调用之间没有依赖关系,可以同时进行。", "tool_calls": [ { "id": "chatcmpl-tool-83653bb310744682", "type": "function", "function": { "name": "get_group_permissions", "arguments": "{\\"group\\": \\"Engineering\\"}" } }, { "id": "chatcmpl-tool-b2ee9d73aef14658", "type": "function", "function": { "name": "get_group_permissions", "arguments": "{\\"group\\": \\"OnCall\\"}" } } ] }, "finish_reason": "tool_calls" } ], "usage": { "prompt_tokens": 440, "completion_tokens": 94, "total_tokens": 534, "prompt_tokens_details": { "cached_tokens": 400 }, "completion_tokens_details": { "reasoning_tokens": 44 } }}
十四、4. 【第 2 轮】回填工具结果
回填工具结果(假设 Engineering:生产库只读;OnCall:生产库读写):
请求:
cURLPythonNode.jsJavaGocurl -X POST 'https://tokenhub.tencentmaas.com/v1/chat/completions' \\ -H 'Content-Type: application/json' \\ -H 'Authorization: Bearer YOUR_API_KEY' \\ -d '{ "model": "hy4", "stream": false, "reasoning_effort": "high", "tool_choice": "auto", "messages": [ /* …前 6 条消息同上,保留全部 reasoning_content,省略… */ { "role": "assistant", "content": "我来查询这两个用户组各自的资源访问权限。", "reasoning_content": "用户想知道 Alice 所属的两个组(Engineering 和 OnCall)分别有哪些资源的访问权限。……这两个调用之间没有依赖关系,可以同时进行。", "tool_calls": [ { "id": "chatcmpl-tool-83653bb310744682", "type": "function", "function": { "name": "get_group_permissions", "arguments": "{\\"group\\": \\"Engineering\\"}" } }, { "id": "chatcmpl-tool-b2ee9d73aef14658", "type": "function", "function": { "name": "get_group_permissions", "arguments": "{\\"group\\": \\"OnCall\\"}" } } ] }, { "role": "tool", "tool_call_id": "chatcmpl-tool-83653bb310744682", "content": "{\\"group\\": \\"Engineering\\", \\"permissions\\": [{\\"resource\\": \\"生产数据库\\", \\"access\\": \\"read-only\\"}, {\\"resource\\": \\"staging环境\\", \\"access\\": \\"read-write\\"}, {\\"resource\\": \\"内部Wiki\\", \\"access\\": \\"read-write\\"}]}" }, { "role": "tool", "tool_call_id": "chatcmpl-tool-b2ee9d73aef14658", "content": "{\\"group\\": \\"OnCall\\", \\"permissions\\": [{\\"resource\\": \\"告警系统\\", \\"access\\": \\"read-write\\"}, {\\"resource\\": \\"生产数据库\\", \\"access\\": \\"read-write\\"}]}" } ], "tools": [ /* 同上,省略 */ ] }'# 接续 step3:把第 3 轮的 assistant 消息(含 reasoning_content + tool_calls)回写,# 然后对两个 tool_call 并行回填工具结果import jsonassistant_msg3 = { "role": "assistant", "content": msg3.content or "", "reasoning_content": getattr(msg3, "reasoning_content", ""), "tool_calls": [ { "id": tc.id, "type": tc.type, "function": { "name": tc.function.name, "arguments": tc.function.arguments, }, } for tc in (msg3.tool_calls or []) ],}messages.append(assistant_msg3)# 业务侧并行执行 get_group_permissions,把两个结果依次回填tool_results = { "Engineering": { "group": "Engineering", "permissions": [ {"resource": "生产数据库", "access": "read-only"}, {"resource": "staging环境", "access": "read-write"}, {"resource": "内部Wiki", "access": "read-write"}, ], }, "OnCall": { "group": "OnCall", "permissions": [ {"resource": "告警系统", "access": "read-write"}, {"resource": "生产数据库", "access": "read-write"}, ], },}for tc in (msg3.tool_calls or []): args = json.loads(tc.function.arguments) result = tool_results[args["group"]] messages.append({ "role": "tool", "tool_call_id": tc.id, "content": json.dumps(result, ensure_ascii=False), })# 第 4 轮:把两个工具结果一起送回模型resp4 = client.chat.completions.create( model="hy4", messages=messages, tools=tools, tool_choice="auto", extra_body={"reasoning_effort": "high"},)msg4 = resp4.choices[0].messageprint("第 4 轮 reasoning_content:", getattr(msg4, "reasoning_content", ""))print("第 4 轮 content:", msg4.content)// 接续 step3:把第 3 轮 assistant 消息与两个工具结果回填messages.push({ role: 'assistant', content: msg3.content || '', reasoning_content: msg3.reasoning_content, tool_calls: msg3.tool_calls,});// 业务侧并行执行 get_group_permissionsconst toolResults = { Engineering: { group: 'Engineering', permissions: [ { resource: '生产数据库', access: 'read-only' }, { resource: 'staging环境', access: 'read-write' }, { resource: '内部Wiki', access: 'read-write' }, ], }, OnCall: { group: 'OnCall', permissions: [ { resource: '告警系统', access: 'read-write' }, { resource: '生产数据库', access: 'read-write' }, ], },};for (const tc of msg3.tool_calls || []) { const args = JSON.parse(tc.function.arguments); messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(toolResults[args.group]), });}const resp4 = await client.chat.completions.create({ model: 'hy4', messages, tools, tool_choice: 'auto', reasoning_effort: 'high',});const msg4 = resp4.choices[0].message;console.log('第 4 轮 reasoning_content:', msg4.reasoning_content);console.log('第 4 轮 content:', msg4.content);// 接续第 3 轮响应 r3:把 assistant 消息(含 reasoning_content + tool_calls)回写JsonObject r3Obj = JsonParser.parseString(r3).getAsJsonObject();JsonObject msg3 = r3Obj.getAsJsonArray("choices").get(0).getAsJsonObject() .getAsJsonObject("message");Map<String, Object> assistant3 = new LinkedHashMap<>();assistant3.put("role", "assistant");assistant3.put("content", msg3.has("content") ? msg3.get("content").getAsString() : "");if (msg3.has("reasoning_content")) { assistant3.put("reasoning_content", msg3.get("reasoning_content").getAsString());}if (msg3.has("tool_calls")) { assistant3.put("tool_calls", GSON.fromJson(msg3.get("tool_calls"), List.class));}messages.add(assistant3);// 业务侧并行执行 get_group_permissionsMap<String, String> toolResults = Map.of( "Engineering", "{\\"group\\": \\"Engineering\\", \\"permissions\\": [{\\"resource\\": \\"生产数据库\\", \\"access\\": \\"read-only\\"}, {\\"resource\\": \\"staging环境\\", \\"access\\": \\"read-write\\"}, {\\"resource\\": \\"内部Wiki\\", \\"access\\": \\"read-write\\"}]}", "OnCall", "{\\"group\\": \\"OnCall\\", \\"permissions\\": [{\\"resource\\": \\"告警系统\\", \\"access\\": \\"read-write\\"}, {\\"resource\\": \\"生产数据库\\", \\"access\\": \\"read-write\\"}]}");for (JsonElement el : msg3.getAsJsonArray("tool_calls")) { JsonObject call = el.getAsJsonObject(); JsonObject args = JsonParser.parseString( call.getAsJsonObject("function").get("arguments").getAsString() ).getAsJsonObject(); String group = args.get("group").getAsString(); messages.add(Map.of( "role", "tool", "tool_call_id", call.get("id").getAsString(), "content", toolResults.get(group) ));}// 第 4 轮:把两个工具结果一起送回模型String r4 = chat(messages, tools);System.out.println("第 4 轮响应:" + r4);// 接续第 3 轮响应 r3:把 assistant 消息(含 reasoning_content + tool_calls)回写msg3Wrap := r3["choices"].([]interface{})[0].(map[string]interface{})msg3 := msg3Wrap["message"].(map[string]interface{})messages = append(messages, msg3)// 业务侧并行执行 get_group_permissionstoolResults := map[string]string{ "Engineering": `{"group": "Engineering", "permissions": [{"resource": "生产数据库", "access": "read-only"}, {"resource": "staging环境", "access": "read-write"}, {"resource": "内部Wiki", "access": "read-write"}]}`, "OnCall": `{"group": "OnCall", "permissions": [{"resource": "告警系统", "access": "read-write"}, {"resource": "生产数据库", "access": "read-write"}]}`,}toolCalls, _ := msg3["tool_calls"].([]interface{})for _, c := range toolCalls { call := c.(map[string]interface{}) fn := call["function"].(map[string]interface{}) var args map[string]interface{} json.Unmarshal([]byte(fn["arguments"].(string)), &args) group := args["group"].(string) messages = append(messages, map[string]interface{}{ "role": "tool", "tool_call_id": call["id"], "content": toolResults[group], })}// 第 4 轮:把两个工具结果一起送回模型r4, _ := chat(messages, tools)fmt.Printf("第 4 轮响应: %+v\\n", r4)
响应:模型完成本轮回答,finish_reason=stop:
{ "id": "REPLACED_ID", "object": "chat.completion", "created": 1783084585, "model": "hy4", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "根据查询结果,这两个用户组的资源访问权限如下:\\n\\n**Engineering 组**\\n- 生产数据库:只读(read-only)\\n- staging环境:读写(read-write)\\n- 内部Wiki:读写(read-write)\\n\\n**OnCall 组**\\n- 告警系统:读写(read-write)\\n- 生产数据库:读写(read-write)\\n\\n**补充说明:** 两个组都对「生产数据库」拥有权限,但级别不同——Engineering 是只读,而 OnCall 是读写。因为 Alice 同时属于这两个组,所以她实际对生产数据库能享有最高级别的**读写**权限。", "reasoning_content": "现在我有了两个组的权限信息:\\n\\nEngineering 组:\\n- 生产数据库: read-only\\n- staging环境: read-write\\n- 内部Wiki: read-write\\n\\nOnCall 组:\\n- 告警系统: read-write\\n- 生产数据库: read-write\\n\\n我需要整理这些信息并回答用户。注意两个组对生产数据库都有权限,但 Engineering 是 read-only,OnCall 是 read-write。由于 Alice 同时属于两个组,实际上她对生产数据库的实际权限应该取并集/最高权限,即 read-write……" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 641, "completion_tokens": 266, "total_tokens": 907, "prompt_tokens_details": { "cached_tokens": 512 }, "completion_tokens_details": { "reasoning_tokens": 140 } }}
十五、5. 【第 3 轮】再次发起提问(保留式思考的核心价值)
追加第 3 个 user 提问。此时完整的 messages 数组如下,注意每工具轮的 reasoning_content 等内容都被原样保留:
[ { "role": "system", "content": "你是一个企业 IT 权限助理 Agent……请始终用中文进行逐步推理与思考。" }, { "role": "user", "content": "Alice 属于哪些用户组?" }, { "role": "assistant", "content": "我来查询 Alice 所属的用户组。", "reasoning_content": "用户问 Alice 属于哪些用户组……只有一个调用,没有依赖。", "tool_calls": [ { "id": "chatcmpl-tool-93cc85d5f381f88d", "type": "function", "function": { "name": "get_user_groups", "arguments": "{\\"user\\": \\"Alice\\"}" } } ] }, { "role": "tool", "tool_call_id": "chatcmpl-tool-93cc85d5f381f88d", "content": "{\\"user\\": \\"Alice\\", \\"groups\\": [\\"Engineering\\", \\"OnCall\\"]}" }, { "role": "assistant", "content": "根据查询结果,Alice 属于以下两个用户组:……", "reasoning_content": "根据工具返回结果,Alice 属于 Engineering 和 OnCall 两个用户组。" }, { "role": "user", "content": "这些组分别有哪些资源的访问权限?" }, { "role": "assistant", "content": "我来查询这两个用户组各自的资源访问权限。", "reasoning_content": "……这两个调用之间没有依赖关系,可以同时进行。", "tool_calls": [ { "id": "chatcmpl-tool-83653bb310744682", "type": "function", "function": { "name": "get_group_permissions", "arguments": "{\\"group\\": \\"Engineering\\"}" } }, { "id": "chatcmpl-tool-b2ee9d73aef14658", "type": "function", "function": { "name": "get_group_permissions", "arguments": "{\\"group\\": \\"OnCall\\"}" } } ] }, { "role": "tool", "tool_call_id": "chatcmpl-tool-83653bb310744682", "content": "{\\"group\\": \\"Engineering\\", \\"permissions\\": [{\\"resource\\": \\"生产数据库\\", \\"access\\": \\"read-only\\"}, …]}" }, { "role": "tool", "tool_call_id": "chatcmpl-tool-b2ee9d73aef14658", "content": "{\\"group\\": \\"OnCall\\", \\"permissions\\": [{\\"resource\\": \\"生产数据库\\", \\"access\\": \\"read-write\\"}, …]}" }, { "role": "assistant", "content": "根据查询结果,这两个用户组的资源访问权限如下:……(含各组权限表)", "reasoning_content": "现在我有了两个组的权限信息:\\nEngineering 组:\\n- 生产数据库: read-only …\\nOnCall 组:\\n- 生产数据库: read-write …" }, // 用户第 3 个提问轮:综合追问 { "role": "user", "content": "那么综合来看,Alice 对『生产数据库』到底拥有什么级别的访问权限?" }]
响应:模型不再重复调用工具,直接把前两轮思考里积累的中间结论链起来(Alice ∈ {Engineering, OnCall} → 推导出两组对生产库的权限为「只读 / 读写」→ 取最高权限 → 即读写权限):
{ "id": "REPLACED_ID", "object": "chat.completion", "created": 1783084590, "model": "hy4", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "基于前面查询到的信息,我可以直接综合判断,无需再次调用工具:\\n\\n- **Engineering 组** → 生产数据库:只读(read-only)\\n- **OnCall 组** → 生产数据库:读写(read-write)\\n\\n由于 Alice **同时属于** Engineering 和 OnCall 两个组,当多个组对同一资源拥有不同级别的权限时,通常取**最高级别**(权限取并集)。\\n\\n因此,综合来看,Alice 对「生产数据库」拥有 **读写(read-write)** 级别的访问权限。", "reasoning_content": "用户问 Alice 对生产数据库的综合访问权限。根据之前的查询结果:\\n\\n- Engineering 组:生产数据库 – read-only(只读)\\n- OnCall 组:生产数据库 – read-write(读写)\\n\\nAlice 同时属于这两个组。综合来看,她应该拥有最高级别的权限,即读写(read-write)。……我不需要再调用工具,因为之前已经获得了所有必要信息。我可以基于已有数据直接回答。" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 925, "completion_tokens": 283, "total_tokens": 1208, "prompt_tokens_details": { "cached_tokens": 624 }, "completion_tokens_details": { "reasoning_tokens": 139 } }}
十七、需要手动传 preserved_thinking 吗?
一般不需要。若无特殊需求,建议不传,平台会自动采用最优策略——请求携带 tools 时默认开启,纯文本对话默认关闭。仅在"工具未通过标准 tools 数组注册"等特殊情形下才需要手动设置为 true。
十八、开启保留式思考后,一定要回填历史 reasoning_content 吗?
是。preserved_thinking 仅控制启停,不会自动处理历史思考的回填。开发者需要将模型上一轮返回的完整、未经修改的 reasoning_content 与工具结果一并原样带回,否则可能影响模型效果并降低缓存命中率。
十九、reasoning_content 回填时可以做摘要压缩吗?
不建议。回填的 reasoning_content 必须与模型生成时完全一致,不要改写、截断或重新排序,否则会降低模型效果并影响缓存命中率。
20、保留式思考会显著增加 token 消耗吗?
从单次请求 body 长度来看会略有增加,但由于上下文前缀更稳定,缓存命中率显著提升,在多轮长流程任务下整体 token 成本通常会下降,同时响应延迟也会降低。