xinfer-ai 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +21 -0
- package/README.md +595 -0
- package/bin/xinfer.js +66 -0
- package/docs/add_model.md +88 -0
- package/docs/claude_code.md +95 -0
- package/docs/docker.md +73 -0
- package/docs/embeddings.md +33 -0
- package/docs/get_started.md +93 -0
- package/docs/goose.md +83 -0
- package/docs/guided_decoding.md +474 -0
- package/docs/kilocode.md +69 -0
- package/docs/mcp_tool_calling.md +155 -0
- package/docs/multimodal.md +37 -0
- package/docs/opencode.md +83 -0
- package/docs/performance.md +65 -0
- package/docs/prefix-cache.md +79 -0
- package/docs/rust_crate.md +119 -0
- package/docs/test_model.md +104 -0
- package/docs/tokenizer_api.md +116 -0
- package/docs/tool_parsing.md +74 -0
- package/docs/xbot.md +110 -0
- package/package.json +49 -0
- package/scripts/install.js +211 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# Guided Decoding
|
|
2
|
+
|
|
3
|
+
This document describes the current guided decoding implementation in `xinfer`.
|
|
4
|
+
|
|
5
|
+
It focuses on:
|
|
6
|
+
- the live request-to-sampling workflow
|
|
7
|
+
- the supported request surfaces
|
|
8
|
+
- how reasoning effort is applied
|
|
9
|
+
- practical usage and validation commands
|
|
10
|
+
|
|
11
|
+
## Current Model
|
|
12
|
+
|
|
13
|
+
Guided decoding is request-scoped.
|
|
14
|
+
|
|
15
|
+
The core engine does not invent grammars on its own. A request either:
|
|
16
|
+
- supplies a constraint grammar
|
|
17
|
+
- gets that constraint grammar composed with a reasoning prefix
|
|
18
|
+
- or runs unconstrained when neither exists
|
|
19
|
+
|
|
20
|
+
The final grammar is stored in `SamplingParams.grammar` and consumed by the runner.
|
|
21
|
+
|
|
22
|
+
## End-to-End Workflow
|
|
23
|
+
|
|
24
|
+
### 1. Request parsing
|
|
25
|
+
|
|
26
|
+
OpenAI-compatible requests can provide a grammar through:
|
|
27
|
+
- `structured_outputs`
|
|
28
|
+
- `response_format`
|
|
29
|
+
- legacy `constraint` plus `constraint_type`
|
|
30
|
+
|
|
31
|
+
Relevant code:
|
|
32
|
+
- `src/server/mod.rs`
|
|
33
|
+
- `src/server/server.rs`
|
|
34
|
+
|
|
35
|
+
The server converts those request fields into `TopLevelGrammar`.
|
|
36
|
+
|
|
37
|
+
Only one constraint source may be set at a time.
|
|
38
|
+
|
|
39
|
+
### 2. Grammar composition
|
|
40
|
+
|
|
41
|
+
The server composes:
|
|
42
|
+
- request constraint grammar
|
|
43
|
+
- optional reasoning grammar prefix
|
|
44
|
+
|
|
45
|
+
The result is a single `TopLevelGrammar` assigned to `SamplingParams.grammar`.
|
|
46
|
+
|
|
47
|
+
If no client-supplied constraint grammar exists, `params.grammar` stays `None`.
|
|
48
|
+
|
|
49
|
+
### 3. Sampling in runner
|
|
50
|
+
|
|
51
|
+
The runner uses the standard llguidance loop:
|
|
52
|
+
|
|
53
|
+
1. build or reuse `GuidanceState` from `SamplingParams.grammar`
|
|
54
|
+
2. call `compute_mask_or_eos()`
|
|
55
|
+
3. mask logits
|
|
56
|
+
4. apply penalties on masked logits
|
|
57
|
+
5. sample once
|
|
58
|
+
6. `commit_token()` into matcher state
|
|
59
|
+
|
|
60
|
+
Failures are fail-soft per sequence:
|
|
61
|
+
- on matcher creation failure, mask failure, or commit failure, guidance is disabled for that sequence
|
|
62
|
+
- normal generation continues for the affected sequence
|
|
63
|
+
|
|
64
|
+
Relevant code:
|
|
65
|
+
- `src/core/runner.rs`
|
|
66
|
+
- `src/utils/guidance.rs`
|
|
67
|
+
|
|
68
|
+
## Supported Request Surfaces
|
|
69
|
+
|
|
70
|
+
### OpenAI-compatible request fields
|
|
71
|
+
|
|
72
|
+
`structured_outputs`
|
|
73
|
+
- `choice`
|
|
74
|
+
- `regex`
|
|
75
|
+
- `json`
|
|
76
|
+
- `grammar`
|
|
77
|
+
- `structural_tag`
|
|
78
|
+
|
|
79
|
+
`response_format`
|
|
80
|
+
- `json_schema`
|
|
81
|
+
- `json_object`
|
|
82
|
+
|
|
83
|
+
Legacy fields
|
|
84
|
+
- `constraint`
|
|
85
|
+
- `constraint_type = regex | lark | json_schema | json`
|
|
86
|
+
|
|
87
|
+
If a request provides none of the above, guided decoding is not enabled.
|
|
88
|
+
|
|
89
|
+
### Claude server
|
|
90
|
+
|
|
91
|
+
Current state:
|
|
92
|
+
- Claude does not expose the same client-supplied grammar request surface as the OpenAI endpoint
|
|
93
|
+
- Claude reasoning is still driven by explicit thinking behavior, not by `reasoning_effort` grammar composition
|
|
94
|
+
- Claude requests therefore do not currently enable guided decoding
|
|
95
|
+
|
|
96
|
+
## Reasoning Effort
|
|
97
|
+
|
|
98
|
+
Reasoning effort is separate from ordinary structured outputs.
|
|
99
|
+
|
|
100
|
+
### Current state
|
|
101
|
+
|
|
102
|
+
The OpenAI path maps `reasoning_effort` into grammar composition when a request constraint exists.
|
|
103
|
+
|
|
104
|
+
Accepted values come from `ReasoningEffort::from_str`:
|
|
105
|
+
- `none`
|
|
106
|
+
- `low`
|
|
107
|
+
- `medium`
|
|
108
|
+
- `normal`
|
|
109
|
+
- `high`
|
|
110
|
+
- `chain_of_thought`
|
|
111
|
+
- `cot`
|
|
112
|
+
- `cove`
|
|
113
|
+
|
|
114
|
+
Non-Python builds also support:
|
|
115
|
+
- `custom:<template>`
|
|
116
|
+
|
|
117
|
+
Relevant code:
|
|
118
|
+
- `src/utils/reasoning.rs`
|
|
119
|
+
- `src/server/server.rs`
|
|
120
|
+
- `src/utils/guidance.rs`
|
|
121
|
+
|
|
122
|
+
### Important behavior
|
|
123
|
+
|
|
124
|
+
Reasoning effort:
|
|
125
|
+
- does not enable chat-template thinking by itself
|
|
126
|
+
- only affects grammar composition
|
|
127
|
+
- is ignored when no request constraint grammar is present
|
|
128
|
+
- only works when reasoning start/end tokens are available
|
|
129
|
+
|
|
130
|
+
If the tokenizer does not expose reasoning markers, the system logs a warning and falls back to the base grammar.
|
|
131
|
+
|
|
132
|
+
### Why this is separate
|
|
133
|
+
|
|
134
|
+
Keep these concerns separate:
|
|
135
|
+
- `thinking`: prompt/template behavior
|
|
136
|
+
- `reasoning_effort`: guided-decoding grammar behavior
|
|
137
|
+
|
|
138
|
+
Server default:
|
|
139
|
+
- when a request omits `thinking` / `enable_thinking`, the Rust CLI defaults to thinking enabled
|
|
140
|
+
- start the Rust server with `--disable-reasoning` to change only that default fallback
|
|
141
|
+
- explicit request values still win over the CLI default
|
|
142
|
+
- `reasoning_effort` may still set `thinking=true` when guided decoding normalization is active
|
|
143
|
+
|
|
144
|
+
That separation avoids changing prompt rendering for request types that do not support OpenAI-style reasoning controls.
|
|
145
|
+
|
|
146
|
+
## Use Cases
|
|
147
|
+
|
|
148
|
+
Present guided decoding from the simplest use case to the most constrained one.
|
|
149
|
+
|
|
150
|
+
### Running the server
|
|
151
|
+
```shell
|
|
152
|
+
xinfer --m Qwen/Qwen3.5-35B-A3B-FP8/ --ui-server --d 0
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Quick map
|
|
156
|
+
|
|
157
|
+
| Goal | Request surface | Typical type |
|
|
158
|
+
|------|-----------------|--------------|
|
|
159
|
+
| Pick one label | `structured_outputs` | `choice` |
|
|
160
|
+
| Enforce text pattern | `structured_outputs` or `constraint` | `regex` |
|
|
161
|
+
| Enforce full object schema | `structured_outputs` or `response_format` | `json` / `json_schema` |
|
|
162
|
+
| Enforce custom grammar | `structured_outputs` or `constraint` | `grammar` / `lark` |
|
|
163
|
+
| Constrain tagged structured output payload | `structured_outputs` | `structural_tag` |
|
|
164
|
+
| Add a reasoning prefix | `reasoning_effort` | `low`, `medium`, `high`, etc. |
|
|
165
|
+
|
|
166
|
+
### 1. Constrain the answer to a fixed set
|
|
167
|
+
|
|
168
|
+
Use this when the model must pick exactly one label.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
172
|
+
-H 'Content-Type: application/json' \
|
|
173
|
+
-d '{
|
|
174
|
+
"messages": [{"role":"user","content":"Classify this sentiment"}],
|
|
175
|
+
"structured_outputs": {
|
|
176
|
+
"choice": ["positive", "negative", "neutral"]
|
|
177
|
+
},
|
|
178
|
+
"max_tokens": 50
|
|
179
|
+
}'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Expected behavior:
|
|
183
|
+
- output is one of the listed strings
|
|
184
|
+
- server log shows `source=structured_outputs type=choice`
|
|
185
|
+
|
|
186
|
+
### 2. Constrain text with regex
|
|
187
|
+
|
|
188
|
+
Use this when the output is text, but must follow a narrow textual pattern.
|
|
189
|
+
|
|
190
|
+
`structured_outputs.regex` example:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
194
|
+
-H 'Content-Type: application/json' \
|
|
195
|
+
-d '{
|
|
196
|
+
"messages": [{"role":"user","content":"Return a 3-digit code"}],
|
|
197
|
+
"structured_outputs": {
|
|
198
|
+
"regex": "^[0-9]{3}$"
|
|
199
|
+
},
|
|
200
|
+
"max_tokens": 10
|
|
201
|
+
}'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Expected behavior:
|
|
205
|
+
- output matches the regex
|
|
206
|
+
- server log shows `source=structured_outputs type=regex`
|
|
207
|
+
|
|
208
|
+
### 3. Constrain to a JSON object with a schema
|
|
209
|
+
|
|
210
|
+
Use this when the model must produce machine-parseable JSON.
|
|
211
|
+
|
|
212
|
+
Exact example:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
216
|
+
-H 'Content-Type: application/json' \
|
|
217
|
+
-d '{
|
|
218
|
+
"messages": [{"role":"user","content":"Generate a user profile"}],
|
|
219
|
+
"structured_outputs": {
|
|
220
|
+
"json": {
|
|
221
|
+
"type": "object",
|
|
222
|
+
"properties": {
|
|
223
|
+
"name": {"type": "string"},
|
|
224
|
+
"age": {"type": "integer", "minimum": 0, "maximum": 150},
|
|
225
|
+
"email": {"type": "string", "pattern": "^[a-z]+@[a-z]+\\.[a-z]+$"}
|
|
226
|
+
},
|
|
227
|
+
"required": ["name", "age", "email"],
|
|
228
|
+
"additionalProperties": false
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
"max_tokens": 500
|
|
232
|
+
}'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Expected behavior:
|
|
236
|
+
- output is valid JSON
|
|
237
|
+
- required fields are present
|
|
238
|
+
- extra properties are rejected by grammar
|
|
239
|
+
|
|
240
|
+
### 4. Use OpenAI-style `response_format`
|
|
241
|
+
|
|
242
|
+
Use this when compatibility with clients that already emit `response_format` matters.
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
246
|
+
-H 'Content-Type: application/json' \
|
|
247
|
+
-d '{
|
|
248
|
+
"messages": [{"role":"user","content":"Return a JSON object"}],
|
|
249
|
+
"response_format": {
|
|
250
|
+
"type": "json_schema",
|
|
251
|
+
"json_schema": {
|
|
252
|
+
"schema": {
|
|
253
|
+
"type": "object",
|
|
254
|
+
"properties": {
|
|
255
|
+
"answer": {"type": "string"}
|
|
256
|
+
},
|
|
257
|
+
"required": ["answer"],
|
|
258
|
+
"additionalProperties": false
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}'
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Expected behavior:
|
|
266
|
+
- output follows the supplied schema
|
|
267
|
+
- server log shows `source=response_format type=json_schema`
|
|
268
|
+
|
|
269
|
+
`json_object` example:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
273
|
+
-H 'Content-Type: application/json' \
|
|
274
|
+
-d '{
|
|
275
|
+
"messages": [{"role":"user","content":"Return any JSON object"}],
|
|
276
|
+
"response_format": {
|
|
277
|
+
"type": "json_object"
|
|
278
|
+
},
|
|
279
|
+
"max_tokens": 200
|
|
280
|
+
}'
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Expected behavior:
|
|
284
|
+
- output is a valid JSON object
|
|
285
|
+
- no custom property schema is enforced beyond object shape
|
|
286
|
+
|
|
287
|
+
### 5. Constrain with custom grammar
|
|
288
|
+
|
|
289
|
+
Use this when the output is not simple JSON schema and needs a formal grammar.
|
|
290
|
+
|
|
291
|
+
Regex example:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
295
|
+
-H 'Content-Type: application/json' \
|
|
296
|
+
-d '{
|
|
297
|
+
"messages": [{"role":"user","content":"Return a 3-digit code"}],
|
|
298
|
+
"constraint": "^[0-9]{3}$",
|
|
299
|
+
"constraint_type": "regex",
|
|
300
|
+
"max_tokens": 10
|
|
301
|
+
}'
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Lark example:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
308
|
+
-H 'Content-Type: application/json' \
|
|
309
|
+
-d '{
|
|
310
|
+
"messages": [{"role":"user","content":"Return yes or no"}],
|
|
311
|
+
"constraint": "start: \"yes\" | \"no\"",
|
|
312
|
+
"constraint_type": "lark",
|
|
313
|
+
"max_tokens": 10
|
|
314
|
+
}'
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Expected behavior:
|
|
318
|
+
- output satisfies the supplied grammar exactly
|
|
319
|
+
- server log shows `source=constraint type=regex` or `type=lark`
|
|
320
|
+
|
|
321
|
+
`structured_outputs.grammar` example:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
325
|
+
-H 'Content-Type: application/json' \
|
|
326
|
+
-d '{
|
|
327
|
+
"messages": [{"role":"user","content":"Return yes or no"}],
|
|
328
|
+
"structured_outputs": {
|
|
329
|
+
"grammar": "start: \"yes\" | \"no\""
|
|
330
|
+
},
|
|
331
|
+
"max_tokens": 10
|
|
332
|
+
}'
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Expected behavior:
|
|
336
|
+
- output follows the supplied Lark grammar
|
|
337
|
+
- server log shows `source=structured_outputs type=grammar`
|
|
338
|
+
|
|
339
|
+
### 6. Constrain structural-tag output
|
|
340
|
+
|
|
341
|
+
Use this when a request wants tagged structured content instead of plain JSON.
|
|
342
|
+
|
|
343
|
+
Example:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
347
|
+
-H 'Content-Type: application/json' \
|
|
348
|
+
-d '{
|
|
349
|
+
"messages": [{"role":"user","content":"Emit a search tool call"}],
|
|
350
|
+
"structured_outputs": {
|
|
351
|
+
"structural_tag": {
|
|
352
|
+
"tag": "tool_call",
|
|
353
|
+
"schema": {
|
|
354
|
+
"type": "object",
|
|
355
|
+
"properties": {
|
|
356
|
+
"name": {"type": "string", "enum": ["search"]},
|
|
357
|
+
"arguments": {
|
|
358
|
+
"type": "object",
|
|
359
|
+
"properties": {
|
|
360
|
+
"query": {"type": "string"}
|
|
361
|
+
},
|
|
362
|
+
"required": ["query"],
|
|
363
|
+
"additionalProperties": false
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
"required": ["name", "arguments"],
|
|
367
|
+
"additionalProperties": false
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
"max_tokens": 200
|
|
372
|
+
}'
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Expected behavior:
|
|
376
|
+
- output follows the tagged structural envelope
|
|
377
|
+
- schema rules still constrain the payload
|
|
378
|
+
|
|
379
|
+
### 7. Add reasoning effort on top of a constraint
|
|
380
|
+
|
|
381
|
+
Use this when you want the output constrained, but still want the model to emit a reasoning block first when the tokenizer supports reasoning markers.
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
385
|
+
-H 'Content-Type: application/json' \
|
|
386
|
+
-d '{
|
|
387
|
+
"messages": [{"role":"user","content":"Classify this sentiment and think first: I am happy!"}],
|
|
388
|
+
"structured_outputs": {
|
|
389
|
+
"choice": ["positive", "negative", "neutral"]
|
|
390
|
+
},
|
|
391
|
+
"reasoning_effort": "low",
|
|
392
|
+
"max_tokens": 1000
|
|
393
|
+
}'
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Expected behavior:
|
|
397
|
+
- the base answer remains grammar-constrained
|
|
398
|
+
- reasoning grammar is prefixed only when reasoning tokens are available
|
|
399
|
+
- if reasoning tokens are missing, the server logs a warning and falls back
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
### Conflict rejection
|
|
403
|
+
|
|
404
|
+
Run:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
408
|
+
-H 'Content-Type: application/json' \
|
|
409
|
+
-d '{
|
|
410
|
+
"messages": [{"role":"user","content":"hi"}],
|
|
411
|
+
"structured_outputs": {"choice": ["a", "b"]},
|
|
412
|
+
"response_format": {
|
|
413
|
+
"type": "json_schema",
|
|
414
|
+
"json_schema": {"schema": {"type": "object"}}
|
|
415
|
+
}
|
|
416
|
+
}'
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Check:
|
|
420
|
+
- request is rejected
|
|
421
|
+
- error says only one of `structured_outputs`, `response_format`, or `constraint` may be set
|
|
422
|
+
|
|
423
|
+
### Unsupported `response_format` rejection
|
|
424
|
+
|
|
425
|
+
Run:
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
429
|
+
-H 'Content-Type: application/json' \
|
|
430
|
+
-d '{
|
|
431
|
+
"messages": [{"role":"user","content":"hi"}],
|
|
432
|
+
"response_format": {
|
|
433
|
+
"type": "xml"
|
|
434
|
+
}
|
|
435
|
+
}'
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Check:
|
|
439
|
+
- request is rejected
|
|
440
|
+
- error mentions only `json_schema` and `json_object` are supported
|
|
441
|
+
|
|
442
|
+
### Empty `structured_outputs` rejection
|
|
443
|
+
|
|
444
|
+
Run:
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
curl -sXPOST localhost:8000/v1/chat/completions \
|
|
448
|
+
-H 'Content-Type: application/json' \
|
|
449
|
+
-d '{
|
|
450
|
+
"messages": [{"role":"user","content":"hi"}],
|
|
451
|
+
"structured_outputs": {}
|
|
452
|
+
}'
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Check:
|
|
456
|
+
- request is rejected
|
|
457
|
+
- error says one of `choice`, `regex`, `json`, `grammar`, or `structural_tag` must be set
|
|
458
|
+
|
|
459
|
+
### Reasoning effort fallback
|
|
460
|
+
|
|
461
|
+
Run the reasoning-effort example above on a model without reasoning tokens.
|
|
462
|
+
|
|
463
|
+
Check:
|
|
464
|
+
- request still completes
|
|
465
|
+
- base constraint still applies
|
|
466
|
+
- warning indicates reasoning tokens were not found
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
## Known Boundaries
|
|
470
|
+
|
|
471
|
+
- Guided decoding is only active when `SamplingParams.grammar` is present.
|
|
472
|
+
- OpenAI currently has the richest client-facing grammar surface.
|
|
473
|
+
- Claude does not currently expose request-level guided decoding.
|
|
474
|
+
- No request-level grammar means no guided decoding.
|
package/docs/kilocode.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Kilo Code + xInfer (OpenAI-compatible endpoint)
|
|
2
|
+
|
|
3
|
+
This guide connects Kilo Code directly to xInfer using the built-in OpenAI-compatible `/v1/chat_completions` API.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Kilo Code -> xInfer (OpenAI-compatible)
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 1) Start xInfer on port 8000
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Rust
|
|
13
|
+
xinfer --m Qwen/Qwen3.5-35B-A3B-FP8 --server --d 0
|
|
14
|
+
|
|
15
|
+
# Different model
|
|
16
|
+
xinfer --m Qwen/Qwen3.5-27B-FP8 --d 0 --server
|
|
17
|
+
|
|
18
|
+
# Python
|
|
19
|
+
python3 -m xinfer.server --m Qwen/Qwen3-Coder-Next-FP8 --d 0,1
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 2) Configure Kilo Code
|
|
23
|
+
|
|
24
|
+
Install Kilo Code (CLI)
|
|
25
|
+
|
|
26
|
+
```shell
|
|
27
|
+
npm install -g @kilocode/cli
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Export config into `~/.config/kilo/config.json`
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"$schema": "https://opencode.ai/config.json",
|
|
36
|
+
"provider": {
|
|
37
|
+
"xinfer": {
|
|
38
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
39
|
+
"name": "xInfer Local",
|
|
40
|
+
"options": {
|
|
41
|
+
"baseURL": "http://localhost:8000/v1"
|
|
42
|
+
},
|
|
43
|
+
"models": {
|
|
44
|
+
"qwen3-coder": {
|
|
45
|
+
"name": "Qwen3 Coder"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"model": "xinfer/qwen3-coder"
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 3) Run Kilo Code
|
|
55
|
+
|
|
56
|
+
run Kilo Code (CLI)
|
|
57
|
+
|
|
58
|
+
```shell
|
|
59
|
+
kilo
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Trouble shooting
|
|
63
|
+
|
|
64
|
+
1. Use the chat logger to monitor detailed interactions between Kilo Code and xInfer.
|
|
65
|
+
|
|
66
|
+
```shell
|
|
67
|
+
# Log into files (in folder ./log)
|
|
68
|
+
export XINFER_CHAT_LOGGER=1
|
|
69
|
+
```
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# MCP Integration and Tool Calling
|
|
2
|
+
|
|
3
|
+
xInfer supports **Model Context Protocol (MCP)** integration, allowing you to easily expose tools from MCP servers to your LLM.
|
|
4
|
+
|
|
5
|
+
**Important:** xInfer follows the standard OpenAI tool calling specification. This means the server **only handles tool definitions and prompt injection**. It does **not** execute tools internally. When a model calls a tool, the generation stops, and the tool call details are returned to the client. The client is responsible for executing the tool and submitting the results back to the server.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
### What is MCP?
|
|
10
|
+
|
|
11
|
+
The **Model Context Protocol (MCP)** is a standardized protocol for connecting LLMs to external tools and services. xInfer supports:
|
|
12
|
+
|
|
13
|
+
- **Stdio transport**: Connect to local MCP servers via command-line processes
|
|
14
|
+
- **HTTP/SSE transport**: Connect to remote MCP servers via HTTP
|
|
15
|
+
- **Multi-server support**: Configure multiple MCP servers with automatic tool name prefixing
|
|
16
|
+
- **Automatic tool injection**: Tool definitions from configured MCP servers are automatically injected into the model's system prompt
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Tool Calling Workflow
|
|
21
|
+
|
|
22
|
+
1. **Configuration**: You configure MCP servers in xInfer (via CLI or `mcp.json`).
|
|
23
|
+
2. **Injection**: xInfer fetches tool definitions from these servers and appends them to the system prompt of your request.
|
|
24
|
+
3. **Generation**: The model generates a tool call.
|
|
25
|
+
4. **Completion**: The stream (or request) finishes with `finish_reason="tool_calls"`.
|
|
26
|
+
5. **Execution (Client-side)**: Your client code receives the tool call, executes the corresponding function (which you must implement or bridge to an MCP client), and sends the result back in a new request.
|
|
27
|
+
|
|
28
|
+
### Example Flow
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import openai
|
|
32
|
+
|
|
33
|
+
client = openai.OpenAI(base_url="http://localhost:8000/v1", api_key="empty")
|
|
34
|
+
|
|
35
|
+
# Request triggers tool injection from configured MCP servers
|
|
36
|
+
response = client.chat.completions.create(
|
|
37
|
+
model="default",
|
|
38
|
+
messages=[{"role": "user", "content": "List files in current directory"}],
|
|
39
|
+
stream=True
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# ... Handle stream ...
|
|
43
|
+
# If model calls a tool, response will contain tool_calls
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Configuration Options
|
|
49
|
+
|
|
50
|
+
### CLI Options
|
|
51
|
+
|
|
52
|
+
| Option | Description |
|
|
53
|
+
|--------|-------------|
|
|
54
|
+
| `--mcp-command` | Path to single MCP server executable |
|
|
55
|
+
| `--mcp-args` | Comma-separated arguments for MCP server |
|
|
56
|
+
| `--mcp-config` | Path to JSON config file for multiple servers |
|
|
57
|
+
|
|
58
|
+
### Config File Format
|
|
59
|
+
|
|
60
|
+
#### Local Servers (Stdio Transport)
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"server_name": {
|
|
66
|
+
"command": "path/to/executable",
|
|
67
|
+
"args": ["arg1", "arg2"],
|
|
68
|
+
"env": {
|
|
69
|
+
"ENV_VAR": "value"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### Remote Servers (HTTP/SSE Transport)
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"server_name": {
|
|
82
|
+
"url": "https://mcp.example.com/api/",
|
|
83
|
+
"headers": {
|
|
84
|
+
"Authorization": "Bearer YOUR_TOKEN",
|
|
85
|
+
"Accept": "text/event-stream"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
> **Note:** Each server must have either `command` (local) or `url` (remote), not both. You can mix local and remote servers in the same config file.
|
|
93
|
+
|
|
94
|
+
### Tool Name Prefixing
|
|
95
|
+
|
|
96
|
+
When using multiple MCP servers, tool names are prefixed with the server name to avoid conflicts:
|
|
97
|
+
|
|
98
|
+
- Server `filesystem` with tool `list_directory` → `filesystem_list_directory`
|
|
99
|
+
- Server `github` with tool `search_repos` → `github_search_repos`
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Tool Call Parsing Architecture
|
|
104
|
+
|
|
105
|
+
xInfer uses a dual-strategy approach for detecting tool calls in streaming responses.
|
|
106
|
+
|
|
107
|
+
### Detection Strategy
|
|
108
|
+
|
|
109
|
+
1. **Token ID-based detection** (preferred): For models with dedicated tool call tokens
|
|
110
|
+
- Uses special token IDs defined in the tokenizer
|
|
111
|
+
- More robust and prevents false positives from text that looks like tool calls
|
|
112
|
+
|
|
113
|
+
2. **Text-based detection** (fallback): For models without special tokens
|
|
114
|
+
- Uses pattern matching for start/end tag strings
|
|
115
|
+
- Applied when token IDs are not available
|
|
116
|
+
|
|
117
|
+
### Model Support Matrix
|
|
118
|
+
|
|
119
|
+
| Model Family | Detection Method | Start Token | End Token |
|
|
120
|
+
|--------------|-----------------|-------------|-----------|
|
|
121
|
+
| Qwen 2.5/3 | Token ID + Text | `<tool_call>` (151657) | `</tool_call>` (151658) |
|
|
122
|
+
| Llama 3/3.1 | Token ID + Text | `<\|python_tag\|>` (128010) | `<\|eom_id\|>` (128008) |
|
|
123
|
+
| Mistral | Token ID + Text | `[TOOL_CALLS]` (9) | `]` |
|
|
124
|
+
| Gemma | Text only | `<start_function_call>` | `<end_function_call>` |
|
|
125
|
+
| Phi4, GLM4 | Text only | `<tool_call>` | `</tool_call>` |
|
|
126
|
+
|
|
127
|
+
### Buffering Logic
|
|
128
|
+
|
|
129
|
+
The streaming tool parser uses a state machine:
|
|
130
|
+
|
|
131
|
+
1. **Normal State**: Tokens stream through. Check for start trigger.
|
|
132
|
+
2. **Buffering State**: On start detection, accumulate tokens silently.
|
|
133
|
+
3. **End Detection**: When end token/text is detected:
|
|
134
|
+
- Parse buffer into structured tool calls
|
|
135
|
+
- If parsing succeeds: return `finish_reason: "tool_calls"`
|
|
136
|
+
- If parsing fails: flush buffered content as normal text
|
|
137
|
+
|
|
138
|
+
### Content Filtering
|
|
139
|
+
|
|
140
|
+
Tool calls are NOT detected inside:
|
|
141
|
+
- **Reasoning blocks** (`<think>...</think>`, `<|think|>...<|/think|>`)
|
|
142
|
+
- **Code blocks** (` ``` `)
|
|
143
|
+
|
|
144
|
+
This prevents false positives from examples or documentation.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Behavior Matrix
|
|
149
|
+
|
|
150
|
+
| Has Request Tools | MCP Configured | Behavior |
|
|
151
|
+
|-------------------|----------------|----------|
|
|
152
|
+
| ❌ | ❌ | Normal generation |
|
|
153
|
+
| ✅ | ❌ | User tools used. Stream finishes at tool call. |
|
|
154
|
+
| ❌ | ✅ | MCP tools injected. Stream finishes at tool call. |
|
|
155
|
+
| ✅ | ✅ | Both User and MCP tools available. Stream finishes at tool call. |
|