Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ response = client.chat.completions.create(
}
}],
tool_choice="auto",
parallel_tool_calls=True, # let the model call several tools in one turn
)

if response.choices[0].message.tool_calls:
Expand Down Expand Up @@ -550,6 +551,9 @@ the `publish` confirmation value.

## Changelog

### Unreleased
- Add `parallel_tool_calls` and `max_completion_tokens` to `chat.completions.create()`.

### v0.3.0
- Add the current Apertis Web Search request fields and typed top-level sources while
retaining legacy Web Search options for compatibility.
Expand Down
29 changes: 29 additions & 0 deletions apertis/resources/chat/completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ def create(
stream: Literal[True],
temperature: float | None = None,
max_tokens: int | None = None,
max_completion_tokens: int | None = None,
top_p: float | None = None,
frequency_penalty: float | None = None,
presence_penalty: float | None = None,
stop: str | Sequence[str] | None = None,
seed: int | None = None,
tools: Sequence[ChatCompletionToolParam] | None = None,
tool_choice: str | dict[str, Any] | None = None,
parallel_tool_calls: bool | None = None,
response_format: dict[str, Any] | None = None,
user: str | None = None,
logprobs: bool | None = None,
Expand Down Expand Up @@ -85,13 +87,15 @@ def create(
stream: Literal[False] = False,
temperature: float | None = None,
max_tokens: int | None = None,
max_completion_tokens: int | None = None,
top_p: float | None = None,
frequency_penalty: float | None = None,
presence_penalty: float | None = None,
stop: str | Sequence[str] | None = None,
seed: int | None = None,
tools: Sequence[ChatCompletionToolParam] | None = None,
tool_choice: str | dict[str, Any] | None = None,
parallel_tool_calls: bool | None = None,
response_format: dict[str, Any] | None = None,
user: str | None = None,
logprobs: bool | None = None,
Expand Down Expand Up @@ -127,13 +131,15 @@ def create(
stream: bool = False,
temperature: float | None = None,
max_tokens: int | None = None,
max_completion_tokens: int | None = None,
top_p: float | None = None,
frequency_penalty: float | None = None,
presence_penalty: float | None = None,
stop: str | Sequence[str] | None = None,
seed: int | None = None,
tools: Sequence[ChatCompletionToolParam] | None = None,
tool_choice: str | dict[str, Any] | None = None,
parallel_tool_calls: bool | None = None,
response_format: dict[str, Any] | None = None,
user: str | None = None,
logprobs: bool | None = None,
Expand Down Expand Up @@ -168,13 +174,15 @@ def create(
stream: bool = False,
temperature: float | None = None,
max_tokens: int | None = None,
max_completion_tokens: int | None = None,
top_p: float | None = None,
frequency_penalty: float | None = None,
presence_penalty: float | None = None,
stop: str | Sequence[str] | None = None,
seed: int | None = None,
tools: Sequence[ChatCompletionToolParam] | None = None,
tool_choice: str | dict[str, Any] | None = None,
parallel_tool_calls: bool | None = None,
response_format: dict[str, Any] | None = None,
user: str | None = None,
logprobs: bool | None = None,
Expand Down Expand Up @@ -208,13 +216,16 @@ def create(
stream: If True, returns a streaming response.
temperature: Sampling temperature between 0 and 2.
max_tokens: Maximum number of tokens to generate.
max_completion_tokens: Upper bound on generated tokens, including
reasoning tokens (newer OpenAI models use this instead of max_tokens).
top_p: Nucleus sampling parameter.
frequency_penalty: Penalty for token frequency.
presence_penalty: Penalty for token presence.
stop: Stop sequences.
seed: Random seed for deterministic results.
tools: A list of tools the model may call.
tool_choice: Controls which tool is called.
parallel_tool_calls: Whether the model may call several tools in one turn.
response_format: Format specification for the response.
user: A unique identifier for the end-user.
logprobs: Whether to return log probabilities.
Expand All @@ -239,13 +250,15 @@ def create(
stream=stream,
temperature=temperature,
max_tokens=max_tokens,
max_completion_tokens=max_completion_tokens,
top_p=top_p,
frequency_penalty=frequency_penalty,
presence_penalty=presence_penalty,
stop=stop,
seed=seed,
tools=tools,
tool_choice=tool_choice,
parallel_tool_calls=parallel_tool_calls,
response_format=response_format,
user=user,
logprobs=logprobs,
Expand Down Expand Up @@ -456,13 +469,15 @@ async def create(
stream: Literal[True],
temperature: float | None = None,
max_tokens: int | None = None,
max_completion_tokens: int | None = None,
top_p: float | None = None,
frequency_penalty: float | None = None,
presence_penalty: float | None = None,
stop: str | Sequence[str] | None = None,
seed: int | None = None,
tools: Sequence[ChatCompletionToolParam] | None = None,
tool_choice: str | dict[str, Any] | None = None,
parallel_tool_calls: bool | None = None,
response_format: dict[str, Any] | None = None,
user: str | None = None,
logprobs: bool | None = None,
Expand Down Expand Up @@ -491,13 +506,15 @@ async def create(
stream: Literal[False] = False,
temperature: float | None = None,
max_tokens: int | None = None,
max_completion_tokens: int | None = None,
top_p: float | None = None,
frequency_penalty: float | None = None,
presence_penalty: float | None = None,
stop: str | Sequence[str] | None = None,
seed: int | None = None,
tools: Sequence[ChatCompletionToolParam] | None = None,
tool_choice: str | dict[str, Any] | None = None,
parallel_tool_calls: bool | None = None,
response_format: dict[str, Any] | None = None,
user: str | None = None,
logprobs: bool | None = None,
Expand Down Expand Up @@ -526,13 +543,15 @@ async def create(
stream: bool = False,
temperature: float | None = None,
max_tokens: int | None = None,
max_completion_tokens: int | None = None,
top_p: float | None = None,
frequency_penalty: float | None = None,
presence_penalty: float | None = None,
stop: str | Sequence[str] | None = None,
seed: int | None = None,
tools: Sequence[ChatCompletionToolParam] | None = None,
tool_choice: str | dict[str, Any] | None = None,
parallel_tool_calls: bool | None = None,
response_format: dict[str, Any] | None = None,
user: str | None = None,
logprobs: bool | None = None,
Expand Down Expand Up @@ -560,13 +579,15 @@ async def create(
stream: bool = False,
temperature: float | None = None,
max_tokens: int | None = None,
max_completion_tokens: int | None = None,
top_p: float | None = None,
frequency_penalty: float | None = None,
presence_penalty: float | None = None,
stop: str | Sequence[str] | None = None,
seed: int | None = None,
tools: Sequence[ChatCompletionToolParam] | None = None,
tool_choice: str | dict[str, Any] | None = None,
parallel_tool_calls: bool | None = None,
response_format: dict[str, Any] | None = None,
user: str | None = None,
logprobs: bool | None = None,
Expand Down Expand Up @@ -595,13 +616,15 @@ async def create(
stream=stream,
temperature=temperature,
max_tokens=max_tokens,
max_completion_tokens=max_completion_tokens,
top_p=top_p,
frequency_penalty=frequency_penalty,
presence_penalty=presence_penalty,
stop=stop,
seed=seed,
tools=tools,
tool_choice=tool_choice,
parallel_tool_calls=parallel_tool_calls,
response_format=response_format,
user=user,
logprobs=logprobs,
Expand Down Expand Up @@ -762,13 +785,15 @@ def _build_request_body(
stream: bool,
temperature: float | None,
max_tokens: int | None,
max_completion_tokens: int | None,
top_p: float | None,
frequency_penalty: float | None,
presence_penalty: float | None,
stop: str | Sequence[str] | None,
seed: int | None,
tools: Sequence[ChatCompletionToolParam] | None,
tool_choice: str | dict[str, Any] | None,
parallel_tool_calls: bool | None,
response_format: dict[str, Any] | None,
user: str | None,
logprobs: bool | None,
Expand Down Expand Up @@ -799,6 +824,8 @@ def _build_request_body(
body["temperature"] = temperature
if max_tokens is not None:
body["max_tokens"] = max_tokens
if max_completion_tokens is not None:
body["max_completion_tokens"] = max_completion_tokens
if top_p is not None:
body["top_p"] = top_p
if frequency_penalty is not None:
Expand All @@ -813,6 +840,8 @@ def _build_request_body(
body["tools"] = list(tools)
if tool_choice is not None:
body["tool_choice"] = tool_choice
if parallel_tool_calls is not None:
body["parallel_tool_calls"] = parallel_tool_calls
if response_format is not None:
body["response_format"] = response_format
if user is not None:
Expand Down
68 changes: 68 additions & 0 deletions tests/test_chat_advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,71 @@ def test_stream_options_include_usage(self, client: Apertis) -> None:

assert response.usage is not None
assert response.usage.total_tokens == 15


_TOOL_CALL_COMPLETION = {
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1234567890,
"model": "gpt-5.4",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "Done."},
"finish_reason": "stop",
}
],
}


class TestToolCallingParams:
"""parallel_tool_calls and max_completion_tokens are top-level request fields."""

@respx.mock
def test_sync_sends_top_level_fields(self, client: Apertis) -> None:
route = respx.post("https://api.apertis.ai/v1/chat/completions").mock(
return_value=httpx.Response(200, json=_TOOL_CALL_COMPLETION)
)

client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Weather in Paris and Tokyo?"}],
parallel_tool_calls=False,
max_completion_tokens=256,
)

body = json.loads(route.calls[0].request.content)
assert body["parallel_tool_calls"] is False
assert body["max_completion_tokens"] == 256
assert "extra_body" not in body

@respx.mock
async def test_async_sends_top_level_fields(self, async_client: AsyncApertis) -> None:
route = respx.post("https://api.apertis.ai/v1/chat/completions").mock(
return_value=httpx.Response(200, json=_TOOL_CALL_COMPLETION)
)

await async_client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Weather in Paris and Tokyo?"}],
parallel_tool_calls=True,
max_completion_tokens=256,
)

body = json.loads(route.calls[0].request.content)
assert body["parallel_tool_calls"] is True
assert body["max_completion_tokens"] == 256

@respx.mock
def test_omitted_when_unset(self, client: Apertis) -> None:
route = respx.post("https://api.apertis.ai/v1/chat/completions").mock(
return_value=httpx.Response(200, json=_TOOL_CALL_COMPLETION)
)

client.chat.completions.create(
model="gpt-5.4", messages=[{"role": "user", "content": "Hi"}]
)

body = json.loads(route.calls[0].request.content)
assert "parallel_tool_calls" not in body
assert "max_completion_tokens" not in body
Loading