工具调用llm输出格式

最后发布时间 : 2026-02-06 10:19:31 浏览量 :
curl -X POST https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
    "model": "qwen-plus",
    "messages": [
        {
            "role": "system",
            "content": "You are a helpful assistant."
        },
        {
            "role": "user", 
            "content": "杭州天气怎么样"
        }
    ],
    "stream":true,
    "tools": [
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "当你想知道现在的时间时非常有用。",
            "parameters": {}
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "当你想查询指定城市的天气时非常有用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "location":{
                        "type": "string",
                        "description": "城市或县区,比如北京市、杭州市、余杭区等。"
                    }
                },
                "required": ["location"]
            }
        }
    }
  ]
}'

data: {
    "choices": [
        {
            "delta": {
                "content": null,
                "tool_calls": [
                    {
                        "index": 0,
                        "id": "call_0bdcc155f2534f65a05cb1",
                        "type": "function",
                        "function": {
                            "name": "get_current_weather",
                            "arguments": "{\"location\":"
                        }
                    }
                ],
                "role": "assistant"
            },
            "finish_reason": null,
            "index": 0,
            "logprobs": null
        }
    ],
    "object": "chat.completion.chunk",
    "usage": null,
    "created": 1770343950,
    "system_fingerprint": null,
    "model": "qwen-plus",
    "id": "chatcmpl-6b9f079d-c440-9fc5-bb6a-963ad8387e02"
}

data: {
    "choices": [
        {
            "delta": {
                "tool_calls": [
                    {
                        "function": {
                            "arguments": " \"杭州市\"}"
                        },
                        "index": 0,
                        "id": "",
                        "type": "function"
                    }
                ]
            },
            "index": 0
        }
    ],
    "object": "chat.completion.chunk",
    "usage": null,
    "created": 1770343950,
    "system_fingerprint": null,
    "model": "qwen-plus",
    "id": "chatcmpl-6b9f079d-c440-9fc5-bb6a-963ad8387e02"
}

data: {
    "choices": [
        {
            "finish_reason": "tool_calls",
            "delta": {},
            "index": 0,
            "logprobs": null
        }
    ],
    "object": "chat.completion.chunk",
    "usage": null,
    "created": 1770343950,
    "system_fingerprint": null,
    "model": "qwen-plus",
    "id": "chatcmpl-6b9f079d-c440-9fc5-bb6a-963ad8387e02"
}

data: [DONE
]

这个例子非常标准,而且比刚才那段更“高级”——这是 LLM 在流式生成 function / tool call。我们还是用同一把刀,严格区分:模型输出 vs API 包装

我先给你一句 总原则,再逐条拆。

一句话总原则(记住这个就够了)

只要是在 choices[].delta 里面、并且是模型“决定的内容”,都属于 LLM 输出
控制流、结束标志、DONE、usage、id、object、model 等,都是 API 厂商加的

tool_calls 场景 下,LLM 输出的不是自然语言,而是:

  • 要不要调用工具
  • 调用哪个工具
  • 参数 JSON 的 token

逐条拆你这 4 个 chunk

① 第一个 chunk(✅ LLM 输出 + ⚙️ API 协议)

"delta": {
  "content": null,
  "tool_calls": [{
    "index": 0,
    "id": "call_0bdcc155f2534f65a05cb1",
    "type": "function",
    "function": {
      "name": "get_current_weather",
      "arguments": "{\"location\":"
    }
  }],
  "role": "assistant"
}

LLM 真正输出的是哪些?

LLM 输出:

  • tool_calls[0].function.name = "get_current_weather"
  • tool_calls[0].function.arguments = "{\"location\":

⚠️ 注意:
argumentstoken 级流式拼 JSON,不是一次性给完

哪些是 API 加的?

API 协议字段:

  • role: "assistant"
  • index
  • id: "call_0bdcc155f2534f65a05cb1"(tool call 的追踪 ID)
  • type: "function"

📌 结论
这是 LLM 决定“我要调用 get_current_weather,并开始写参数”

② 第二个 chunk(✅ 纯 LLM 输出)

"delta": {
  "tool_calls": [{
    "function": {
      "arguments": " \"杭州市\"}"
    }
  }]
}

含义

  • LLM 在 继续补全 JSON 参数
  • 拼起来就是:
{"location": "杭州市"}

📌 结论:

  • arguments 内容是 100% LLM 输出
  • index / id / type 仍然是 API 协议

③ 第三个 chunk(❌ 非 LLM 内容)

"finish_reason": "tool_calls",
"delta": {}

含义

  • API 告诉你:

模型已经 完成 tool call 的生成

  • tool_calls 是结束原因,不是模型说的话

📌 结论:

完全不是 LLM 输出
API 控制信号

④ 最后一条 data: [DONE]

data: [DONE]

含义

  • SSE 连接结束
  • 流结束

📌 结论:

不是 LLM 输出
SSE 协议层结束标记

汇总成一张“你写 SDK 必备”的表

字段是否 LLM 输出说明
delta.content普通文本 token
delta.tool_calls[].function.name模型选择的工具
delta.tool_calls[].function.arguments模型生成的参数(流式 JSON)
delta.roleAPI 协议
tool_calls[].idAPI 追踪 ID
finish_reason = tool_callsAPI 结束信号
data: [DONE]SSE 结束

正确的客户端处理逻辑(重点)

1️⃣ tool_calls 要 按 index 累加

// pseudo
args[index] += delta.tool_calls[i].function.arguments

2️⃣ 看到 finish_reason == "tool_calls"

👉 不要再等 content
👉 立即执行工具

3️⃣ tool_calls 和 content 是互斥的

  • 有 tool_calls → 没自然语言
  • 有自然语言 → finish_reason = stop

一个很关键的“理解层结论”

LLM 并不是“调用函数”

它只是 生成了一段结构化 token

👉 “要调用哪个工具 + 参数长什么样”

真正调用函数,是你 / API 网关做的

如果你愿意,下一步我可以直接帮你:

  • 🔧 写一个 Go / Python 的 streaming tool_calls 解析器
  • 🔀 设计一个 统一 Event(TextChunk / ToolCall / Done)模型
  • 🧠 解释 为什么 arguments 会被拆成这种奇怪的 JSON 片段

你现在已经在 实现 LLM Runtime 层 了,不是普通使用者 😄