Skip to content

Commit 74425da

Browse files
authored
fix(examples): update multi-turn examples to current renderer API (#68)
1 parent 1569dc9 commit 74425da

5 files changed

Lines changed: 144 additions & 50 deletions

File tree

examples/sglang/multiturn_generate_sglang.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import os
2323

2424
import sglang as sgl
25+
from renderers.configs import Qwen35RendererConfig
2526
from renderers.gpt_oss import GptOssRenderer
2627
from renderers.qwen35 import Qwen35Renderer
2728
from transformers import AutoTokenizer
@@ -52,7 +53,9 @@
5253
def make_renderer(model: str, enable_thinking: bool | None):
5354
tokenizer = AutoTokenizer.from_pretrained(model, trust_remote_code=False)
5455
if model.startswith("Qwen/Qwen3.5-"):
55-
return Qwen35Renderer(tokenizer, enable_thinking=enable_thinking)
56+
return Qwen35Renderer(
57+
tokenizer, Qwen35RendererConfig(enable_thinking=enable_thinking)
58+
)
5659
if model == "openai/gpt-oss-20b":
5760
return GptOssRenderer(tokenizer)
5861
raise ValueError(f"unsupported demo model: {model}")
@@ -62,8 +65,9 @@ def print_parsed(label: str, turn: str, parsed) -> None:
6265
print(f"\n[{label}] {turn}")
6366
if parsed.reasoning_content:
6467
print(f"reasoning: {parsed.reasoning_content[:240]}")
65-
if parsed.tool_calls:
66-
print(f"tool_calls: {json.dumps(parsed.tool_calls, ensure_ascii=False)}")
68+
for tc in parsed.tool_calls:
69+
# ``parse_response`` returns ``ParsedToolCall`` dataclasses, not dicts.
70+
print(f"tool_call: {tc.name}({tc.arguments}) [{tc.status.value}]")
6771
if parsed.content:
6872
print(f"content: {parsed.content}")
6973

@@ -141,21 +145,33 @@ def main() -> None:
141145
if parsed1.reasoning_content:
142146
assistant["reasoning_content"] = parsed1.reasoning_content
143147
if parsed1.tool_calls:
144-
assistant["tool_calls"] = parsed1.tool_calls
148+
# Convert the parsed dataclasses back to OpenAI-format tool_calls.
149+
assistant["tool_calls"] = [
150+
{
151+
"id": tc.id or f"call_{idx}",
152+
"type": "function",
153+
"function": {
154+
"name": tc.name,
155+
"arguments": tc.arguments
156+
if isinstance(tc.arguments, str)
157+
else json.dumps(tc.arguments),
158+
},
159+
}
160+
for idx, tc in enumerate(parsed1.tool_calls)
161+
]
145162
messages.append(assistant)
146163

147164
if parsed1.tool_calls:
148165
new_messages = []
149166
for idx, tool_call in enumerate(parsed1.tool_calls):
150-
fn = tool_call.get("function") or tool_call
151-
tool_args = fn.get("arguments") or {}
167+
tool_args = tool_call.arguments or {}
152168
if isinstance(tool_args, str):
153169
tool_args = json.loads(tool_args)
154170
new_messages.append(
155171
{
156172
"role": "tool",
157-
"tool_call_id": tool_call.get("id", f"call_{idx}"),
158-
"name": fn.get("name", "multiply"),
173+
"tool_call_id": tool_call.id or f"call_{idx}",
174+
"name": tool_call.name or "multiply",
159175
"content": json.dumps(
160176
{"result": int(tool_args["a"]) * int(tool_args["b"])}
161177
),
@@ -167,11 +183,14 @@ def main() -> None:
167183
]
168184

169185
# Turn 2: bridge extends prompt_ids + completion1 exactly.
170-
bridged_ids = renderer.bridge_to_next_turn(
186+
# ``bridge_to_next_turn`` returns a ``RenderedTokens`` (or None); the
187+
# extended id stream is on ``.token_ids``.
188+
bridged = renderer.bridge_to_next_turn(
171189
prompt_ids, completion1, new_messages, tools=TOOLS
172190
)
173-
if bridged_ids is None:
191+
if bridged is None:
174192
raise RuntimeError("bridge_to_next_turn returned None")
193+
bridged_ids = bridged.token_ids
175194
assert bridged_ids[: len(prompt_ids) + len(completion1)] == (
176195
prompt_ids + completion1
177196
)

examples/sglang/online_multiturn_sglang.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
import httpx
4646
from renderers.base import Renderer
47+
from renderers.configs import Qwen35RendererConfig
4748
from renderers.gpt_oss import GptOssRenderer
4849
from renderers.qwen35 import Qwen35Renderer
4950
from transformers import AutoTokenizer
@@ -71,7 +72,9 @@
7172
def make_renderer(model: str, enable_thinking: bool | None) -> Renderer:
7273
tokenizer = AutoTokenizer.from_pretrained(model, trust_remote_code=False)
7374
if model.startswith("Qwen/Qwen3.5-"):
74-
return Qwen35Renderer(tokenizer, enable_thinking=enable_thinking)
75+
return Qwen35Renderer(
76+
tokenizer, Qwen35RendererConfig(enable_thinking=enable_thinking)
77+
)
7578
if model == "openai/gpt-oss-20b":
7679
return GptOssRenderer(tokenizer)
7780
raise ValueError(f"unsupported demo model: {model}")
@@ -116,8 +119,9 @@ def print_parsed(label: str, turn: str, parsed) -> None:
116119
print(f"\n[{label}] {turn}")
117120
if parsed.reasoning_content:
118121
print(f"reasoning: {parsed.reasoning_content[:240]}")
119-
if parsed.tool_calls:
120-
print(f"tool_calls: {json.dumps(parsed.tool_calls, ensure_ascii=False)}")
122+
for tc in parsed.tool_calls:
123+
# ``parse_response`` returns ``ParsedToolCall`` dataclasses, not dicts.
124+
print(f"tool_call: {tc.name}({tc.arguments}) [{tc.status.value}]")
121125
if parsed.content:
122126
print(f"content: {parsed.content}")
123127

@@ -164,21 +168,33 @@ async def run_one(
164168
if parsed1.reasoning_content:
165169
assistant["reasoning_content"] = parsed1.reasoning_content
166170
if parsed1.tool_calls:
167-
assistant["tool_calls"] = parsed1.tool_calls
171+
# Convert the parsed dataclasses back to OpenAI-format tool_calls.
172+
assistant["tool_calls"] = [
173+
{
174+
"id": tc.id or f"call_{idx}",
175+
"type": "function",
176+
"function": {
177+
"name": tc.name,
178+
"arguments": tc.arguments
179+
if isinstance(tc.arguments, str)
180+
else json.dumps(tc.arguments),
181+
},
182+
}
183+
for idx, tc in enumerate(parsed1.tool_calls)
184+
]
168185
messages.append(assistant)
169186

170187
if parsed1.tool_calls:
171188
new_messages: list[dict[str, Any]] = []
172189
for idx, tool_call in enumerate(parsed1.tool_calls):
173-
fn = tool_call.get("function") or tool_call
174-
tool_args = fn.get("arguments") or {}
190+
tool_args = tool_call.arguments or {}
175191
if isinstance(tool_args, str):
176192
tool_args = json.loads(tool_args)
177193
new_messages.append(
178194
{
179195
"role": "tool",
180-
"tool_call_id": tool_call.get("id", f"call_{idx}"),
181-
"name": fn.get("name", "multiply"),
196+
"tool_call_id": tool_call.id or f"call_{idx}",
197+
"name": tool_call.name or "multiply",
182198
"content": json.dumps(
183199
{"result": int(tool_args["a"]) * int(tool_args["b"])}
184200
),
@@ -190,11 +206,14 @@ async def run_one(
190206
]
191207

192208
# Turn 2: bridge extends prompt_ids + completion1 exactly.
193-
bridged_ids = renderer.bridge_to_next_turn(
209+
# ``bridge_to_next_turn`` returns a ``RenderedTokens`` (or None); the
210+
# extended id stream is on ``.token_ids``.
211+
bridged = renderer.bridge_to_next_turn(
194212
prompt_ids, completion1, new_messages, tools=TOOLS
195213
)
196-
if bridged_ids is None:
214+
if bridged is None:
197215
raise RuntimeError("bridge_to_next_turn returned None")
216+
bridged_ids = bridged.token_ids
198217
assert bridged_ids[: len(prompt_ids) + len(completion1)] == (
199218
prompt_ids + completion1
200219
)

examples/tinker/multiturn_generate_tinker.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import os
2323

2424
import tinker
25+
from renderers.configs import Qwen35RendererConfig
2526
from renderers.gpt_oss import GptOssRenderer
2627
from renderers.qwen35 import Qwen35Renderer
2728
from tinker import types
@@ -53,7 +54,9 @@
5354
def make_renderer(model: str, enable_thinking: bool | None):
5455
tokenizer = AutoTokenizer.from_pretrained(model, trust_remote_code=False)
5556
if model.startswith("Qwen/Qwen3.5-"):
56-
return Qwen35Renderer(tokenizer, enable_thinking=enable_thinking)
57+
return Qwen35Renderer(
58+
tokenizer, Qwen35RendererConfig(enable_thinking=enable_thinking)
59+
)
5760
if model == "openai/gpt-oss-20b":
5861
return GptOssRenderer(tokenizer)
5962
raise ValueError(f"unsupported demo model: {model}")
@@ -63,8 +66,9 @@ def print_parsed(label: str, turn: str, parsed) -> None:
6366
print(f"\n[{label}] {turn}")
6467
if parsed.reasoning_content:
6568
print(f"reasoning: {parsed.reasoning_content[:240]}")
66-
if parsed.tool_calls:
67-
print(f"tool_calls: {json.dumps(parsed.tool_calls, ensure_ascii=False)}")
69+
for tc in parsed.tool_calls:
70+
# ``parse_response`` returns ``ParsedToolCall`` dataclasses, not dicts.
71+
print(f"tool_call: {tc.name}({tc.arguments}) [{tc.status.value}]")
6872
if parsed.content:
6973
print(f"content: {parsed.content}")
7074

@@ -131,21 +135,33 @@ async def main() -> None:
131135
if parsed1.reasoning_content:
132136
assistant["reasoning_content"] = parsed1.reasoning_content
133137
if parsed1.tool_calls:
134-
assistant["tool_calls"] = parsed1.tool_calls
138+
# Convert the parsed dataclasses back to OpenAI-format tool_calls.
139+
assistant["tool_calls"] = [
140+
{
141+
"id": tc.id or f"call_{idx}",
142+
"type": "function",
143+
"function": {
144+
"name": tc.name,
145+
"arguments": tc.arguments
146+
if isinstance(tc.arguments, str)
147+
else json.dumps(tc.arguments),
148+
},
149+
}
150+
for idx, tc in enumerate(parsed1.tool_calls)
151+
]
135152
messages.append(assistant)
136153

137154
if parsed1.tool_calls:
138155
new_messages = []
139156
for idx, tool_call in enumerate(parsed1.tool_calls):
140-
fn = tool_call.get("function") or tool_call
141-
tool_args = fn.get("arguments") or {}
157+
tool_args = tool_call.arguments or {}
142158
if isinstance(tool_args, str):
143159
tool_args = json.loads(tool_args)
144160
new_messages.append(
145161
{
146162
"role": "tool",
147-
"tool_call_id": tool_call.get("id", f"call_{idx}"),
148-
"name": fn.get("name", "multiply"),
163+
"tool_call_id": tool_call.id or f"call_{idx}",
164+
"name": tool_call.name or "multiply",
149165
"content": json.dumps(
150166
{"result": int(tool_args["a"]) * int(tool_args["b"])}
151167
),
@@ -157,11 +173,14 @@ async def main() -> None:
157173
]
158174

159175
# Turn 2: bridge extends prompt_ids + completion1 exactly.
160-
bridged_ids = renderer.bridge_to_next_turn(
176+
# ``bridge_to_next_turn`` returns a ``RenderedTokens`` (or None); the
177+
# extended id stream is on ``.token_ids``.
178+
bridged = renderer.bridge_to_next_turn(
161179
prompt_ids, completion1, new_messages, tools=TOOLS
162180
)
163-
if bridged_ids is None:
181+
if bridged is None:
164182
raise RuntimeError("bridge_to_next_turn returned None")
183+
bridged_ids = bridged.token_ids
165184
assert bridged_ids[: len(prompt_ids) + len(completion1)] == (
166185
prompt_ids + completion1
167186
)

examples/transformers/multiturn_generate_transformers.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import torch
2727
from transformers import AutoModelForCausalLM, AutoTokenizer
2828

29+
from renderers.configs import Qwen35RendererConfig
2930
from renderers.gpt_oss import GptOssRenderer
3031
from renderers.qwen35 import Qwen35Renderer
3132

@@ -55,7 +56,8 @@
5556
def make_renderer(model: str, enable_thinking: bool | None):
5657
tokenizer = AutoTokenizer.from_pretrained(model, trust_remote_code=False)
5758
if model.startswith("Qwen/Qwen3.5-"):
58-
return Qwen35Renderer(tokenizer, enable_thinking=enable_thinking), tokenizer
59+
config = Qwen35RendererConfig(enable_thinking=enable_thinking)
60+
return Qwen35Renderer(tokenizer, config), tokenizer
5961
if model == "openai/gpt-oss-20b":
6062
return GptOssRenderer(tokenizer), tokenizer
6163
raise ValueError(f"unsupported demo model: {model}")
@@ -65,8 +67,9 @@ def print_parsed(label: str, turn: str, parsed) -> None:
6567
print(f"\n[{label}] {turn}")
6668
if parsed.reasoning_content:
6769
print(f"reasoning: {parsed.reasoning_content[:240]}")
68-
if parsed.tool_calls:
69-
print(f"tool_calls: {json.dumps(parsed.tool_calls, ensure_ascii=False)}")
70+
for tc in parsed.tool_calls:
71+
# ``parse_response`` returns ``ParsedToolCall`` dataclasses, not dicts.
72+
print(f"tool_call: {tc.name}({tc.arguments}) [{tc.status.value}]")
7073
if parsed.content:
7174
print(f"content: {parsed.content}")
7275

@@ -139,21 +142,33 @@ def main() -> None:
139142
if parsed1.reasoning_content:
140143
assistant["reasoning_content"] = parsed1.reasoning_content
141144
if parsed1.tool_calls:
142-
assistant["tool_calls"] = parsed1.tool_calls
145+
# Convert the parsed dataclasses back to OpenAI-format tool_calls.
146+
assistant["tool_calls"] = [
147+
{
148+
"id": tc.id or f"call_{idx}",
149+
"type": "function",
150+
"function": {
151+
"name": tc.name,
152+
"arguments": tc.arguments
153+
if isinstance(tc.arguments, str)
154+
else json.dumps(tc.arguments),
155+
},
156+
}
157+
for idx, tc in enumerate(parsed1.tool_calls)
158+
]
143159
messages.append(assistant)
144160

145161
if parsed1.tool_calls:
146162
new_messages = []
147163
for idx, tool_call in enumerate(parsed1.tool_calls):
148-
fn = tool_call.get("function") or tool_call
149-
tool_args = fn.get("arguments") or {}
164+
tool_args = tool_call.arguments or {}
150165
if isinstance(tool_args, str):
151166
tool_args = json.loads(tool_args)
152167
new_messages.append(
153168
{
154169
"role": "tool",
155-
"tool_call_id": tool_call.get("id", f"call_{idx}"),
156-
"name": fn.get("name", "multiply"),
170+
"tool_call_id": tool_call.id or f"call_{idx}",
171+
"name": tool_call.name or "multiply",
157172
"content": json.dumps(
158173
{"result": int(tool_args["a"]) * int(tool_args["b"])}
159174
),
@@ -165,11 +180,14 @@ def main() -> None:
165180
]
166181

167182
# Turn 2: bridge extends prompt_ids + completion1 exactly.
168-
bridged_ids = renderer.bridge_to_next_turn(
183+
# ``bridge_to_next_turn`` returns a ``RenderedTokens`` (or None); the
184+
# extended id stream is on ``.token_ids``.
185+
bridged = renderer.bridge_to_next_turn(
169186
prompt_ids, completion1, new_messages, tools=TOOLS
170187
)
171-
if bridged_ids is None:
188+
if bridged is None:
172189
raise RuntimeError("bridge_to_next_turn returned None")
190+
bridged_ids = bridged.token_ids
173191
assert bridged_ids[: len(prompt_ids) + len(completion1)] == (
174192
prompt_ids + completion1
175193
)

0 commit comments

Comments
 (0)