Skip to content

Commit 1943347

Browse files
Add multi-turn tool message probe
1 parent 3b728a8 commit 1943347

4 files changed

Lines changed: 76 additions & 3 deletions

File tree

bin/deepseek-compat-kit.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,16 @@ async function probeEndpoint(args) {
452452
expectStream: true,
453453
}));
454454

455+
report.checks.push(await runProbeCheck({
456+
name: "multi_turn_tool_messages",
457+
capability: "multi_turn_tool_messages",
458+
description: "Endpoint accepts a follow-up request containing assistant tool_calls, reasoning_content, and tool results.",
459+
impact: "Multi-turn tool-calling agents can pass DeepSeek reasoning_content back alongside tool results.",
460+
recommendation: "If this warns or fails, confirm that the framework preserves reasoning_content and that the provider accepts DeepSeek tool-call message history.",
461+
request: buildMultiTurnToolProbeRequest(model),
462+
baseUrl,
463+
}));
464+
455465
report.checks.push(await runProbeCheck({
456466
name: "strict_schema_request",
457467
capability: "strict_schema",
@@ -609,6 +619,53 @@ function buildProbeRequest({ model, stream }) {
609619
};
610620
}
611621

622+
function buildMultiTurnToolProbeRequest(model) {
623+
return {
624+
model,
625+
messages: [
626+
{ role: "user", content: "Use the weather tool for Paris." },
627+
{
628+
role: "assistant",
629+
content: null,
630+
reasoning_content: "I need to call the weather tool before answering.",
631+
tool_calls: [{
632+
id: "call_probe_weather",
633+
type: "function",
634+
function: {
635+
name: "get_weather",
636+
arguments: "{\"city\":\"Paris\"}",
637+
},
638+
}],
639+
},
640+
{
641+
role: "tool",
642+
tool_call_id: "call_probe_weather",
643+
content: "{\"city\":\"Paris\",\"weather\":\"sunny\"}",
644+
},
645+
{ role: "user", content: "Reply with exactly: ok" },
646+
],
647+
tools: [{
648+
type: "function",
649+
function: {
650+
name: "get_weather",
651+
description: "Return simple weather data for a city.",
652+
parameters: {
653+
type: "object",
654+
properties: {
655+
city: {
656+
type: "string",
657+
description: "City name.",
658+
},
659+
},
660+
required: ["city"],
661+
additionalProperties: false,
662+
},
663+
},
664+
}],
665+
max_tokens: 8,
666+
};
667+
}
668+
612669
function buildStrictSchemaProbeRequest(model) {
613670
return {
614671
model,

docs/capability-probe.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Use `--profile` to tune report guidance:
6363
| --- | --- | --- |
6464
| `chat_completions` | Minimal non-streaming `POST /chat/completions` | Verifies the basic OpenAI-compatible request path. |
6565
| `streaming` | `stream: true` with event-stream-like response | Verifies whether streaming clients can parse incremental responses. |
66+
| `multi_turn_tool_messages` | Follow-up request containing assistant `tool_calls`, `reasoning_content`, and a matching `tool` result | Verifies whether Agent loops can pass DeepSeek reasoning content back through multi-turn tool-call history. |
6667
| `strict_schema` | Minimal strict tool schema request | Verifies whether tool-calling agents can send DeepSeek strict-mode compatible schemas. |
6768

6869
## Reading the Report
@@ -92,6 +93,12 @@ If `streaming` warns or fails:
9293
- Check whether a relay buffers responses instead of returning `text/event-stream`.
9394
- Re-run the probe after changing provider settings.
9495

96+
If `multi_turn_tool_messages` warns or fails:
97+
98+
- Confirm that the framework preserves `reasoning_content` from the previous assistant tool-call turn.
99+
- Confirm that the provider accepts assistant messages containing both `tool_calls` and `reasoning_content`.
100+
- Re-run the same probe directly against the official DeepSeek endpoint to separate framework, relay, and self-hosted endpoint behavior.
101+
95102
If `strict_schema` warns or fails:
96103

97104
- Run `compile-schema --dry-run` on generated tool schemas.

docs/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ You can also run a small functional endpoint probe against a mock or self-hosted
2424
npx deepseek-compat-kit probe --endpoint http://127.0.0.1:9000 --model mock-model --out ./capability-report.json --markdown ./Capability_Report.md
2525
```
2626

27-
`probe` checks basic chat completions, streaming response shape, and a minimal strict tool schema request. It is not a benchmark or load test. The Markdown report is designed for team handoff or upstream issue triage.
27+
`probe` checks basic chat completions, streaming response shape, multi-turn tool-call message history with `reasoning_content`, and a minimal strict tool schema request. It is not a benchmark or load test. The Markdown report is designed for team handoff or upstream issue triage.
2828

2929
Then use the real proxy against DeepSeek:
3030

test/cli.test.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,10 @@ test("compile-schema dry-run prints repair plan without writing files", () => {
177177
});
178178

179179
test("probe writes endpoint capability report against mock upstream", async (t) => {
180+
const requestBodies = [];
180181
const mock = http.createServer((request, response) => {
181182
collectRequestJson(request).then((body) => {
183+
requestBodies.push(body);
182184
const pathname = new URL(request.url, "http://127.0.0.1").pathname;
183185
if (request.method !== "POST" || pathname !== "/chat/completions") {
184186
response.writeHead(404, { "content-type": "application/json" });
@@ -232,30 +234,37 @@ test("probe writes endpoint capability report against mock upstream", async (t)
232234
assert.equal(report.profile_guidance.name, "Third-party relay or API gateway");
233235
assert.match(report.profile_guidance.strict_schema_hint, /relay preserves DeepSeek strict schema semantics/);
234236
assert.equal(report.summary.status, "PASS");
235-
assert.equal(report.summary.passed, 3);
237+
assert.equal(report.summary.passed, 4);
236238
assert.deepEqual(report.summary.capabilities, {
237239
chat_completions: "PASS",
238240
streaming: "PASS",
241+
multi_turn_tool_messages: "PASS",
239242
strict_schema: "PASS",
240243
});
241244
assert.deepEqual(report.checks.map((check) => check.name), [
242245
"chat_completions",
243246
"streaming",
247+
"multi_turn_tool_messages",
244248
"strict_schema_request",
245249
]);
246250
assert.deepEqual(report.checks.map((check) => check.capability), [
247251
"chat_completions",
248252
"streaming",
253+
"multi_turn_tool_messages",
249254
"strict_schema",
250255
]);
251-
assert.match(report.checks[2].recommendation, /compile-schema/);
256+
assert.match(report.checks[2].recommendation, /reasoning_content/);
257+
assert.match(report.checks[3].recommendation, /compile-schema/);
258+
assert.ok(requestBodies.some((body) => body.messages?.some((message) => message.reasoning_content)));
259+
assert.ok(requestBodies.some((body) => body.messages?.some((message) => message.role === "tool" && message.tool_call_id === "call_probe_weather")));
252260

253261
const markdown = fs.readFileSync(markdownPath, "utf8");
254262
assert.match(markdown, /# DeepSeek CompatKit Capability Report/);
255263
assert.match(markdown, /## Profile Guidance/);
256264
assert.match(markdown, /Third-party relay or API gateway/);
257265
assert.match(markdown, /Status: \*\*PASS\*\*/);
258266
assert.match(markdown, /\| `chat_completions` \| `chat_completions` \| PASS \| 200 \|/);
267+
assert.match(markdown, /\| `multi_turn_tool_messages` \| `multi_turn_tool_messages` \| PASS \| 200 \|/);
259268
assert.match(markdown, /## Recommendations/);
260269
assert.match(markdown, /No immediate compatibility issues/);
261270
});

0 commit comments

Comments
 (0)