# 工具调用 (/docs/cloud/services/model/guides/tool-calling)



模型返回工具名称和参数，由你的程序执行工具，再把结果交回模型。收到工具调用还不是完成回答。

本例查询本地演示数据：Paris 的温度固定为 22°C，不访问天气服务。`deepseek-flash` 在 `GET /openai/v1/models` 的 `supported_parameters` 中列有 `tools` 和 `tool_choice`；下面的两轮调用于 2026-10-12 验证通过。

## 1. 准备终端 [#1-准备终端]

将 Praxis 云 API Key 放入环境变量 `PRAXIS_API_KEY`；获取方式见 [Key 与鉴权](/docs/cloud/account/api-keys)。不要把 Key 写入代码。准备 Python 和 [uv](https://docs.astral.sh/uv/getting-started/installation/)；TypeScript 示例使用 [Bun](https://bun.sh/docs/installation)。

```bash
test -n "$PRAXIS_API_KEY"
```

期望：退出码为 0。示例默认使用 `https://api.deepraxis.ai`。切换环境时，将 `PRAXIS_API_BASE` 设为相应 API 域名，不带末尾斜杠或 API 路径。

将每段 Python 保存为标明的文件名，以 `uv run -q --no-project --with openai --with anthropic python <文件名>` 执行。各段代码独立运行。

## 2. 定义工具并完成两轮调用 [#2-定义工具并完成两轮调用]

保存为 `tool-calling.py`，运行 `uv run -q --no-project --with openai --with anthropic python tool-calling.py`。

代码先声明 `get_temperature` 的名称、描述和参数，再要求模型调用它；验证参数后执行本地查询，将结果和原始 `tool_call_id` 一起回填，最后请求文字回答。两轮都设置 `reasoning_effort="none"`。

```python
import json
import os
from openai import OpenAI

client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.ai') + '/openai/v1')
tools = [{'type':'function','function':{
    'name':'get_temperature', 'description':'Read the demo temperature for Paris.',
    'parameters':{'type':'object','properties':{'city':{'type':'string','enum':['Paris']}},
        'required':['city'],'additionalProperties':False}}}]
messages = [{'role':'user','content':'Use get_temperature for Paris, then tell me the temperature in Celsius.'}]
first = client.chat.completions.create(model='deepseek-flash', messages=messages,
    tools=tools, reasoning_effort='none', tool_choice={'type':'function','function':{'name':'get_temperature'}}, max_tokens=1024)
print('X-Request-Id:', first._request_id)
message = first.choices[0].message
assert first.choices[0].finish_reason == 'tool_calls' and message.tool_calls
messages.append(message.model_dump(exclude_none=True))
for call in message.tool_calls:
    assert call.function.name == 'get_temperature'
    args = json.loads(call.function.arguments)
    assert args == {'city':'Paris'}, args
    result = {'city':args['city'], 'temperature_c':22}
    messages.append({'role':'tool','tool_call_id':call.id,'content':json.dumps(result)})
second = client.chat.completions.create(model='deepseek-flash', messages=messages,
    tools=tools, reasoning_effort='none', tool_choice='none', max_tokens=1024)
print('X-Request-Id:', second._request_id)
answer = second.choices[0].message.content or ''
assert second.choices[0].finish_reason == 'stop' and '22' in answer, answer
print(answer)
```

期望：第一轮 `finish_reason=tool_calls`，函数参数是 `{"city":"Paris"}`；第二轮回答包含 `22`，`finish_reason=stop`；退出码为 0。

保留第一轮完整 assistant 消息，为每个工具调用返回一条 `role="tool"` 消息。不要重造调用 ID，也不要只把结果作为普通用户文字发回。参数是 JSON 字符串，需要解析和校验；不要直接执行模型提供的任意函数名。这里用 `tool_choice="none"` 明确结束工具阶段；使用 `auto` 的实际应用应允许多轮工具调用，并设置轮数上限。

## 3. 用 TypeScript 执行同一流程 [#3-用-typescript-执行同一流程]

保存为 `tool-calling.ts`，运行 `bun tool-calling.ts`；使用内置 `fetch`，无需安装 SDK。

```typescript
import assert from 'node:assert/strict';
const base = process.env.PRAXIS_API_BASE ?? 'https://api.deepraxis.ai';
async function chat(body: object) {
  const response = await fetch(`${base}/openai/v1/chat/completions`, {
    method: 'POST', headers: {'Authorization': `Bearer ${process.env.PRAXIS_API_KEY}`, 'Content-Type': 'application/json'},
    body: JSON.stringify({model: 'deepseek-flash', max_tokens: 1024, reasoning_effort: 'none', ...body}),
  });
  console.log('X-Request-Id:', response.headers.get('x-request-id'));
  const data = await response.json();
  assert(response.ok, JSON.stringify(data));
  return data;
}
const tools = [{type: 'function', function: {name: 'get_temperature',
  description: 'Read the demo temperature for Paris.',
  parameters: {type: 'object', properties: {city: {type: 'string', enum: ['Paris']}},
    required: ['city'], additionalProperties: false}}}];
const messages: any[] = [{role: 'user', content: 'Use get_temperature for Paris, then tell me the temperature in Celsius.'}];
const first = await chat({messages, tools, tool_choice: {type: 'function', function: {name: 'get_temperature'}}});
const choice = first.choices[0];
assert.equal(choice.finish_reason, 'tool_calls');
assert(choice.message.tool_calls?.length);
messages.push(choice.message);
for (const call of choice.message.tool_calls) {
  assert.equal(call.function.name, 'get_temperature');
  const args = JSON.parse(call.function.arguments);
  assert.deepEqual(args, {city: 'Paris'});
  messages.push({role: 'tool', tool_call_id: call.id,
    content: JSON.stringify({city: args.city, temperature_c: 22})});
}
const second = await chat({messages, tools, tool_choice: 'none'});
assert.equal(second.choices[0].finish_reason, 'stop');
assert(second.choices[0].message.content.includes('22'));
console.log(second.choices[0].message.content);
```

期望：完成相同的工具调用与回填，最终回答包含 `22`，退出码为 0。

## 4. 使用 Anthropic Messages [#4-使用-anthropic-messages]

Messages 用 `input_schema` 定义参数，返回 `tool_use` 内容块和 `stop_reason="tool_use"`。将完整 assistant 内容带回后，在下一条 `role="user"` 消息中放入 `tool_result`，用 `tool_use_id` 对应原调用。本例的 `claude-opus-5` 已实测通过。

保存为 `tool-calling-anthropic.py`，运行 `uv run -q --no-project --with openai --with anthropic python tool-calling-anthropic.py`。

```python
import json
import os
from anthropic import Anthropic

client = Anthropic(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.ai') + '/anthropic')
tools = [{'name':'get_temperature','description':'Read the demo temperature for Paris.',
    'input_schema':{'type':'object','properties':{'city':{'type':'string','enum':['Paris']}},
        'required':['city'],'additionalProperties':False}}]
messages = [{'role':'user','content':'Use get_temperature for Paris, then tell me the temperature in Celsius.'}]
raw = client.messages.with_raw_response.create(model='claude-opus-5', max_tokens=1024,
    tools=tools, tool_choice={'type':'tool','name':'get_temperature'}, messages=messages)
print('X-Request-Id:', raw.headers.get('x-request-id'))
first = raw.parse()
assert first.stop_reason == 'tool_use'
messages.append({'role':'assistant','content':[block.model_dump(exclude_none=True) for block in first.content]})
results = []
for block in first.content:
    if block.type == 'tool_use':
        assert block.name == 'get_temperature' and block.input == {'city':'Paris'}
        results.append({'type':'tool_result','tool_use_id':block.id,
            'content':json.dumps({'city':'Paris','temperature_c':22})})
assert results
messages.append({'role':'user','content':results})
raw = client.messages.with_raw_response.create(model='claude-opus-5', max_tokens=1024,
    tools=tools, tool_choice={'type':'none'}, messages=messages)
print('X-Request-Id:', raw.headers.get('x-request-id'))
second = raw.parse()
answer = ''.join(block.text for block in second.content if block.type == 'text')
assert second.stop_reason == 'end_turn' and '22' in answer, answer
print(answer)
```

期望：第一轮请求工具，第二轮返回包含 `22` 的文本，`stop_reason=end_turn`，退出码为 0。

## 实测失败与处理 [#实测失败与处理]

强制指定函数、但没有关闭思考时，`deepseek-flash` 返回 HTTP 400：

> tool\_choice a named function is not supported while thinking is enabled on this model. Set reasoning effort to "none" or use tool\_choice "auto".

本页采用已验证的解决办法：显式设置 `reasoning_effort="none"`。仅第一轮设置、第二轮省略时，还曾收到 HTTP 400：

> The selected thinking setting is not supported on this model route.

两轮都保留 `reasoning_effort="none"` 后通过。不要在重试时丢掉该参数。

协议细节：[OpenAI](https://developers.openai.com/api/docs/guides/function-calling) · [Anthropic](https://platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools)。

[API 参考 →](/docs/api/compatible/openai) · [Cookbook →](/docs/cookbook)
