Skip to content

Commit b27355e

Browse files
authored
Improve OpenAI server tool calling
* Improve OpenAI server tool calling * Reject incomplete tool transcripts
1 parent ecb6d3b commit b27355e

50 files changed

Lines changed: 1904 additions & 537 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
## Unreleased
2+
3+
* Switched the OpenAI-compatible server example default to Unsloth's Qwen3.6
4+
27B `UD-Q4_K_XL` GGUF, added model-specific non-thinking and thinking sampler profiles,
5+
and documented its text-only / desktop-memory requirements. The server keeps
6+
thinking opt-in through the `enable_thinking` request field and now uses
7+
Relic 1.2.0.
8+
9+
* Removed the example server's nonstandard automatic tool-execution loop. Its
10+
Chat Completions endpoint now consistently returns model-emitted `tool_calls`
11+
for client-side execution, accepts standard assistant/tool follow-up
12+
transcripts, honors named function `tool_choice`, and documents the full
13+
request sequence with runnable Swagger examples. Server model inputs now load
14+
through `LlamaEngine.loadModelSource(...)`, sharing the core runtime's source
15+
resolution and cache behavior.
16+
117
## 0.8.15
218

319
* Added clipboard media attachments to the runnable chat app. Desktop and web

example/llamadart_server/README.md

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ This example now follows a feature-first structure:
2727
- `lib/src/features/openai_api/` - OpenAI-compatible HTTP server and docs
2828
- `lib/src/features/chat_completion/` - chat request model, parser, mapper, and
2929
completion use cases
30-
- `lib/src/features/model_management/` - model path resolution and download
30+
- Model paths, URLs, and Hugging Face sources load through
31+
`LlamaEngine.loadModelSource(...)`, using the core runtime's cache and
32+
download policy
3133
- `lib/src/features/server_engine/` - engine contract + llama engine adapter
3234
- `lib/src/features/shared/` - shared API error types
3335
- `lib/src/bootstrap/` - CLI argument parsing and runtime wiring
@@ -38,30 +40,34 @@ Public APIs are exported directly from `lib/llamadart_server.dart`.
3840

3941
- Supports a single generation at a time (returns 429 while busy)
4042
- Supports `n = 1` only
41-
- By default, tools are passed through to the model prompt only (no server-side
42-
execution). Use `--enable-tool-execution` to enable an example built-in tool
43-
loop.
43+
- Function tools follow the standard client-managed Chat Completions flow: the
44+
server returns `tool_calls`, your client executes them, then submits the
45+
assistant tool-call message and matching `role: "tool"` result in a new
46+
request. The server never executes application tools.
4447
- No legacy Completions endpoint
48+
- The default Qwen3.6 27B GGUF is loaded text-only; this example does not
49+
download or load its separate vision projector.
4550

4651
## Run
4752

4853
```bash
4954
dart pub get
50-
dart run llamadart_server --model /path/to/model.gguf
55+
dart run llamadart_server
5156
```
5257

53-
Use a remote GGUF URL instead of a path if you want automatic download.
58+
With no `--model` override, the first launch downloads the default 17.6 GB
59+
Unsloth model. Pass `--model /path/to/model.gguf` to use a local GGUF instead.
60+
You can also pass a remote GGUF URL or an `hf://owner/repo/path` source for
61+
automatic resolution and download.
5462

5563
Optional flags:
5664

5765
- `--model-id` (default: `llamadart-local`)
5866
- `--host` (default: `127.0.0.1`)
5967
- `--port` (default: `8080`)
6068
- `--api-key` (optional)
61-
- `--context-size` (default: `4096`)
69+
- `--context-size` (default: `16384`)
6270
- `--gpu-layers` (default: `999`)
63-
- `--enable-tool-execution` (default: disabled; enables built-in demo handlers)
64-
- `--max-tool-rounds` (default: `5`; used only when tool execution is enabled)
6571
- `--log` (enable verbose Dart + HTTP request logs; native logs stay error-only)
6672

6773
### Exit codes
@@ -72,24 +78,50 @@ Optional flags:
7278

7379
### Sampling defaults
7480

75-
When omitted in a request body, this example server applies a stable default
76-
`GenerationParams` baseline:
81+
When omitted in a request body, this example server applies the supported
82+
subset of Qwen3.6 27B's non-thinking text profile:
7783

78-
- `penalty = 1.0`
79-
- `top_p = 0.95`
80-
- `min_p = 0.05`
84+
- `temperature = 0.7`
85+
- `top_k = 20`
86+
- `top_p = 0.8`
87+
- `min_p = 0.0`
88+
- `repetition penalty = 1.0`
8189

8290
Request-provided sampling fields (for example `temperature`, `top_p`, `seed`,
8391
`max_tokens`) override these defaults per call.
8492

93+
Qwen also recommends a presence penalty, but this example's current backend
94+
does not expose presence-penalty control. Do not substitute repetition penalty
95+
for it; they have different effects.
96+
97+
### Default model and thinking mode
98+
99+
The default is Unsloth's `Qwen3.6-27B-UD-Q4_K_XL.gguf` (about 17.6 GB). Plan
100+
for at least 32 GB of free unified memory or VRAM plus runtime headroom; use
101+
`--gpu-layers 0` to force CPU inference when GPU memory is insufficient.
102+
The 16,384-token context is a practical server default that leaves room for
103+
normal chat;
104+
increase it cautiously because larger contexts need substantially more memory.
105+
106+
Qwen3.6 thinks by default, but this API server intentionally disables thinking
107+
unless a request sets `"enable_thinking": true`, so ordinary calls return
108+
assistant `content` promptly. Thinking responses use a separate
109+
`reasoning_content` channel and the Qwen thinking profile (`temperature: 1.0`,
110+
`top_p: 0.95`). `max_tokens` covers both reasoning and the final answer; the
111+
server's inherited default is 4,096 tokens. Use streaming and raise the limit
112+
for thought-intensive requests while leaving room for the prompt in the
113+
16,384-token context. A request that ends during reasoning can correctly have
114+
`reasoning_content` but no final `content`.
115+
85116
## API Examples
86117

87118
### 0. OpenAPI and Swagger UI
88119

89120
- OpenAPI JSON: `http://127.0.0.1:8080/openapi.json`
90121
- Swagger UI: `http://127.0.0.1:8080/docs`
91-
- Swagger includes ready-made chat request examples (basic, streaming, tools)
92-
and embeddings examples under `POST /v1/embeddings`.
122+
- Swagger includes ready-made chat request examples for basic and streaming
123+
completions, initial tool calls, streamed tool calls, thinking plus streamed
124+
tool calls, and the client-executed tool-result follow-up.
93125

94126
```bash
95127
curl http://127.0.0.1:8080/openapi.json
@@ -115,7 +147,8 @@ curl http://127.0.0.1:8080/v1/chat/completions \
115147
{"role": "system", "content": "You are concise."},
116148
{"role": "user", "content": "Give me one sentence about Seoul."}
117149
],
118-
"max_tokens": 128
150+
"max_tokens": 128,
151+
"enable_thinking": false
119152
}'
120153
```
121154

@@ -133,14 +166,40 @@ curl -N http://127.0.0.1:8080/v1/chat/completions \
133166
}'
134167
```
135168

136-
### 4. With API key
169+
### 4. Tool calling (client-executed)
170+
171+
This server uses the standard OpenAI Chat Completions sequence:
172+
173+
1. Send the user messages, `tools`, and a `tool_choice`.
174+
2. Receive an assistant response with `finish_reason: "tool_calls"` and
175+
`message.tool_calls`.
176+
3. Execute each function in your application.
177+
4. Append the entire assistant tool-call message and one `role: "tool"` message
178+
per result. Each result must use the exact returned `tool_call_id`.
179+
5. Send the expanded transcript in a new `POST /v1/chat/completions` request.
180+
181+
Set `parallel_tool_calls: true` to permit multiple calls in one response when
182+
the active model template supports it; append a result for every returned call.
183+
184+
Use Swagger's **Tool call: request a function** example for step 1 and
185+
**Tool call: submit the function result** for step 5. Swagger cannot transfer
186+
a runtime call ID between requests, so replace `call_weather_example` and its
187+
illustrative assistant message with the actual response from step 1. The
188+
minimal standard tool-result message does not need a `name` field.
189+
190+
For a streaming tool call, accumulate `delta.tool_calls` fragments by index
191+
until the terminal `finish_reason` is `tool_calls`; then follow the same steps.
192+
The **Tool call: reasoning + function request (SSE)** Swagger example also
193+
demonstrates Qwen's optional `reasoning_content` stream.
194+
195+
### 5. With API key
137196

138197
```bash
139198
curl http://127.0.0.1:8080/v1/models \
140199
-H "Authorization: Bearer YOUR_KEY"
141200
```
142201

143-
### 5. Embeddings
202+
### 6. Embeddings
144203

145204
```bash
146205
curl http://127.0.0.1:8080/v1/embeddings \

example/llamadart_server/lib/llamadart_server.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/// Public entry point for the llamadart server example package.
22
library;
33

4-
export 'src/features/model_management/model_management.dart';
54
export 'src/features/shared/shared.dart';
65
export 'src/features/chat_completion/chat_completion.dart';
76
export 'src/features/embeddings/embeddings.dart';

example/llamadart_server/lib/src/bootstrap/cli/server_cli_arg_parser.dart

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import 'package:args/args.dart';
22
import 'package:llamadart/llamadart.dart';
33

44
const String _defaultModelUrl =
5-
'https://huggingface.co/unsloth/Qwen3.5-0.8B-GGUF/resolve/main/'
6-
'Qwen3.5-0.8B-Q4_K_M.gguf?download=true';
5+
'https://huggingface.co/unsloth/Qwen3.6-27B-GGUF/resolve/main/'
6+
'Qwen3.6-27B-UD-Q4_K_XL.gguf?download=true';
77

88
/// Builds the CLI argument parser.
99
ArgParser buildServerCliArgParser() {
@@ -12,7 +12,7 @@ ArgParser buildServerCliArgParser() {
1212
'model',
1313
abbr: 'm',
1414
defaultsTo: _defaultModelUrl,
15-
help: 'Path or URL to a GGUF model.',
15+
help: 'Path, URL, or hf:// model source.',
1616
)
1717
..addOption(
1818
'model-id',
@@ -35,26 +35,14 @@ ArgParser buildServerCliArgParser() {
3535
)
3636
..addOption(
3737
'context-size',
38-
defaultsTo: '4096',
38+
defaultsTo: '16384',
3939
help: 'Model context size in tokens.',
4040
)
4141
..addOption(
4242
'gpu-layers',
4343
defaultsTo: '${ModelParams.maxGpuLayers}',
4444
help: 'Number of layers to offload to GPU.',
4545
)
46-
..addFlag(
47-
'enable-tool-execution',
48-
defaultsTo: false,
49-
help:
50-
'Enable server-side execution loop for model-emitted tool calls. '
51-
'This example includes built-in mock handlers for common demo tools.',
52-
)
53-
..addOption(
54-
'max-tool-rounds',
55-
defaultsTo: '5',
56-
help: 'Maximum tool-call rounds per request when execution is enabled.',
57-
)
5846
..addFlag(
5947
'log',
6048
abbr: 'g',

example/llamadart_server/lib/src/bootstrap/cli/server_cli_config.dart

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,6 @@ class ServerCliConfig {
2626
/// Whether verbose Dart/request logs are enabled.
2727
final bool enableDartLogs;
2828

29-
/// Whether server-side tool execution is enabled.
30-
final bool enableToolExecution;
31-
32-
/// Maximum tool-call rounds per request.
33-
final int maxToolRounds;
34-
3529
/// Creates a parsed runtime config.
3630
const ServerCliConfig({
3731
required this.modelInput,
@@ -42,8 +36,6 @@ class ServerCliConfig {
4236
required this.contextSize,
4337
required this.gpuLayers,
4438
required this.enableDartLogs,
45-
required this.enableToolExecution,
46-
required this.maxToolRounds,
4739
});
4840

4941
/// Whether auth is enabled.

example/llamadart_server/lib/src/bootstrap/cli/server_cli_config_parser.dart

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,6 @@ ServerCliConfig parseServerCliConfig(ArgResults results) {
1212
throw ArgumentError('Invalid --host value: $host');
1313
}
1414

15-
final maxToolRounds = _parseIntOption(
16-
results['max-tool-rounds'] as String,
17-
'max-tool-rounds',
18-
);
19-
if (maxToolRounds < 1) {
20-
throw ArgumentError('`max-tool-rounds` must be >= 1.');
21-
}
22-
2315
return ServerCliConfig(
2416
modelInput: results['model'] as String,
2517
modelId: results['model-id'] as String,
@@ -32,8 +24,6 @@ ServerCliConfig parseServerCliConfig(ArgResults results) {
3224
),
3325
gpuLayers: _parseIntOption(results['gpu-layers'] as String, 'gpu-layers'),
3426
enableDartLogs: results['log'] as bool,
35-
enableToolExecution: results['enable-tool-execution'] as bool,
36-
maxToolRounds: maxToolRounds,
3727
);
3828
}
3929

example/llamadart_server/lib/src/bootstrap/example_tool_invoker.dart

Lines changed: 0 additions & 90 deletions
This file was deleted.

0 commit comments

Comments
 (0)